diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index b41ce36..9aae6fa 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -17,10 +17,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' server-id: github # Value of the distributionManagement/repository/id field of the pom.xml settings-path: ${{ github.workspace }} # location for the settings.xml file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index d8d81a8..ab4f43c 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -21,17 +21,22 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: maven - name: Build with Maven - run: mvn -B package --file source/pom.xml + run: mvn -B package --file pom.xml # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - - name: Update dependency graph + - name: Update dependency graph API uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 with: - directory: source + directory: api + + - name: Update dependency graph CLI + uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 + with: + directory: cli diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..1c3509c --- /dev/null +++ b/api/.gitignore @@ -0,0 +1 @@ +mockserver_keystore* diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 0000000..0608443 --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + + net.klesatschke.threema + msgapi-sdk-java + 1.1.5-SNAPSHOT + + api + Threema Messsage Gateway API + + + MIT-License + http://opensource.org/licenses/mit-license.php + repo + + + https://github.com/lordyavin/threema-msgapi-sdk-java + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.assertj + assertj-core + test + + + org.mock-server + mockserver-junit-jupiter-no-dependencies + test + + + org.mock-server + mockserver-client-java-no-dependencies + test + + + org.mockito + mockito-junit-jupiter + test + + + commons-io + commons-io + + + org.apache.commons + commons-text + + + com.google.code.gson + gson + + + org.projectlombok + lombok + provided + + + eu.neilalexander + jnacl + + + org.apache.logging.log4j + log4j-core + + + + + github + GitHub lordyavin Apache Maven Packages + https://maven.pkg.github.com/lordyavin/threema-msgapi-sdk-java + + + \ No newline at end of file diff --git a/api/src/main/java/net/klesatschke/threema/api/APIConnector.java b/api/src/main/java/net/klesatschke/threema/api/APIConnector.java new file mode 100644 index 0000000..d1c2e42 --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/APIConnector.java @@ -0,0 +1,458 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Redirect; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Map; +import java.util.Optional; + +import javax.net.ssl.HttpsURLConnection; + +import lombok.extern.log4j.Log4j2; +import net.klesatschke.threema.api.exceptions.ClientError; +import net.klesatschke.threema.api.exceptions.ServerError; +import net.klesatschke.threema.api.exceptions.ThreemaError; +import net.klesatschke.threema.api.results.CapabilityResult; +import net.klesatschke.threema.api.results.EncryptResult; +import net.klesatschke.threema.api.results.UploadResult; + +/** Facilitates HTTPS communication with the Threema Message API. */ +@Log4j2 +public class APIConnector { + private static final int BUFFER_SIZE = 16384; + + public interface ProgressListener { + + /** + * Update the progress of an upload/download process. + * + * @param progress in percent (0..100) + */ + void updateProgress(int progress); + } + + public class InputStreamLength { + public final InputStream inputStream; + public final int length; + + public InputStreamLength(InputStream inputStream, int length) { + this.inputStream = inputStream; + this.length = length; + } + } + + private final String apiUrl; + private final PublicKeyStore publicKeyStore; + private final String apiIdentity; + private final String secret; + private HttpClient httpClient; + + public APIConnector(String apiIdentity, String secret, PublicKeyStore publicKeyStore) { + this(apiIdentity, secret, "https://msgapi.threema.ch/", publicKeyStore); + } + + public APIConnector( + String apiIdentity, String secret, String apiUrl, PublicKeyStore publicKeyStore) { + this.apiIdentity = apiIdentity; + this.secret = secret; + this.apiUrl = apiUrl; + this.publicKeyStore = publicKeyStore; + httpClient = HttpClient.newBuilder().followRedirects(Redirect.NEVER).build(); + } + + /** + * Send a text message with server-side encryption. + * + * @param to recipient ID + * @param text message text (max. 3500 bytes) + * @return message ID + * @throws IOException if a communication or server error occurs + */ + public String sendTextMessageSimple(String to, String text) throws IOException { + + var postParams = makeRequestParams(); + postParams.put("to", to); + postParams.put("text", text); + + return doPost(new URL(this.apiUrl + "send_simple"), postParams); + } + + /** + * Send an end-to-end encrypted message. + * + * @param to recipient ID + * @param nonce nonce used for encryption (24 bytes) + * @param box encrypted message data (max. 4000 bytes) + * @return message ID + * @throws IOException if a communication or server error occurs + */ + public String sendE2EMessage(String to, byte[] nonce, byte[] box) throws IOException { + + var postParams = makeRequestParams(); + postParams.put("to", to); + postParams.put("nonce", DataUtils.byteArrayToHexString(nonce)); + postParams.put("box", DataUtils.byteArrayToHexString(box)); + + return doPost(new URL(this.apiUrl + "send_e2e"), postParams); + } + + /** + * Lookup an ID by phone number. The phone number will be hashed before being sent to the server. + * + * @param phoneNumber the phone number in E.164 format + * @return the ID, or null if not found + * @throws IOException if a communication or server error occurs + */ + public String lookupPhone(String phoneNumber) throws IOException { + var getParams = makeRequestParams(); + var phoneHash = CryptTool.hashPhoneNo(phoneNumber); + + try { + return doGet( + URI.create( + this.apiUrl + "lookup/phone_hash/" + DataUtils.byteArrayToHexString(phoneHash)), + getParams); + } catch (ThreemaError e) { + switch (e.getResponse().statusCode()) { + case 400: + throw new ClientError(e.getResponse(), "the hash length is wrong"); + case 401: + throw new ClientError(e.getResponse(), "API identity or secret are incorrect"); + case 404: + throw new ClientError(e.getResponse(), "no matching ID could be found"); + case 500: + throw new ServerError(e.getResponse(), "a temporary internal server error occurs"); + default: + throw e; + } + } + } + + /** + * Lookup an ID by email address. The email address will be hashed before being sent to the + * server. + * + * @param email the email address + * @return the ID, or null if not found + * @throws IOException if a communication or server error occurs + */ + public String lookupEmail(String email) throws IOException { + + try { + var getParams = makeRequestParams(); + + var emailHash = CryptTool.hashEmail(email); + + return doGet( + URI.create( + this.apiUrl + "lookup/email_hash/" + DataUtils.byteArrayToHexString(emailHash)), + getParams); + } catch (FileNotFoundException e) { + return null; + } + } + + /** + * Lookup a public key by ID. + * + * @param id the ID whose public key is desired + * @return the corresponding public key, or null if not found + * @throws IOException if a communication or server error occurs + */ + public byte[] lookupKey(String id) throws IOException { + var key = this.publicKeyStore.getPublicKey(id); + if (key == null) { + try { + var getParams = makeRequestParams(); + var pubkeyHex = doGet(URI.create(this.apiUrl + "pubkeys/" + id), getParams); + key = DataUtils.hexStringToByteArray(pubkeyHex); + + if (key != null) { + this.publicKeyStore.save(id, key); + } + } catch (FileNotFoundException e) { + return new byte[0]; + } + } + return key; + } + + /** + * Lookup the capabilities of a ID + * + * @param threemaId The ID whose capabilities should be checked + * @return The capabilities, or null if not found + * @throws IOException + */ + public CapabilityResult lookupKeyCapability(String threemaId) throws IOException { + var res = doGet(URI.create(this.apiUrl + "capabilities/" + threemaId), makeRequestParams()); + if (res != null) { + return new CapabilityResult(threemaId, res.split(",")); + } + return null; + } + + public Integer lookupCredits() throws IOException { + var res = doGet(URI.create(this.apiUrl + "credits"), makeRequestParams()); + if (res != null) { + return Integer.valueOf(res); + } + return null; + } + /** + * Upload a file. + * + * @param fileEncryptionResult The result of the file encryption (i.e. encrypted file data) + * @return the result of the upload + * @throws IOException + */ + public UploadResult uploadFile(EncryptResult fileEncryptionResult) throws IOException { + + var attachmentName = "blob"; + var attachmentFileName = "blob.file"; + var crlf = "\r\n"; + var twoHyphens = "--"; + + var chars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); + var rand = new SecureRandom(); + var count = rand.nextInt(11) + 30; + var boundary = new StringBuilder(count); + for (var i = 0; i < count; i++) { + boundary.append(chars[rand.nextInt(chars.length)]); + } + + var queryString = makeUrlEncoded(makeRequestParams()); + var url = new URL(this.apiUrl + "upload_blob?" + queryString); + + var connection = (HttpsURLConnection) url.openConnection(); + connection.setDoOutput(true); + connection.setDoInput(true); + connection.setUseCaches(false); + + connection.setRequestMethod("POST"); + connection.setRequestProperty("Connection", "Keep-Alive"); + connection.setRequestProperty("Cache-Control", "no-cache"); + connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); + + var request = new DataOutputStream(connection.getOutputStream()); + + request.writeBytes(twoHyphens + boundary + crlf); + request.writeBytes( + "Content-Disposition: form-data; name=\"" + + attachmentName + + "\";filename=\"" + + attachmentFileName + + "\"" + + crlf); + request.writeBytes(crlf); + request.write(fileEncryptionResult.getResult()); + request.writeBytes(crlf); + request.writeBytes(twoHyphens + boundary + twoHyphens + crlf); + + String response = null; + var responseCode = connection.getResponseCode(); + + if (responseCode == 200) { + var is = connection.getInputStream(); + var br = new BufferedReader(new InputStreamReader(is)); + response = br.readLine(); + br.close(); + } + + connection.disconnect(); + + return new UploadResult( + responseCode, response != null ? DataUtils.hexStringToByteArray(response) : null); + } + + /** + * Download a file given its blob ID. + * + * @param blobId The blob ID of the file + * @return Encrypted file data + * @throws IOException + */ + public byte[] downloadFile(byte[] blobId) throws IOException { + return this.downloadFile(blobId, null); + } + + /** + * Download a file given its blob ID. + * + * @param blobId The blob ID of the file + * @param progressListener An object that will receive progress information, or null + * @return Encrypted file data + * @throws IOException + */ + public byte[] downloadFile(byte[] blobId, ProgressListener progressListener) throws IOException { + var queryString = makeUrlEncoded(makeRequestParams()); + var blobUrl = + new URL( + String.format( + "%sblobs/%s?%s", this.apiUrl, DataUtils.byteArrayToHexString(blobId), queryString)); + + var connection = (HttpsURLConnection) blobUrl.openConnection(); + connection.setConnectTimeout(20 * 1000); + connection.setReadTimeout(20 * 1000); + connection.setDoOutput(false); + + var inputStream = connection.getInputStream(); + var contentLength = connection.getContentLength(); + var isl = new InputStreamLength(inputStream, contentLength); + + /* Content length known? */ + byte[] blob; + if (isl.length != -1) { + blob = new byte[isl.length]; + var offset = 0; + int readed; + + while (offset < isl.length + && (readed = isl.inputStream.read(blob, offset, isl.length - offset)) != -1) { + offset += readed; + + if (progressListener != null) { + progressListener.updateProgress(100 * offset / isl.length); + } + } + + if (offset != isl.length) { + throw new IOException( + "Unexpected read size. current: " + offset + ", excepted: " + isl.length); + } + } else { + /* Content length is unknown - need to read until EOF */ + + var bos = new ByteArrayOutputStream(); + var buffer = new byte[BUFFER_SIZE]; + + int read; + while ((read = isl.inputStream.read(buffer)) != -1) { + bos.write(buffer, 0, read); + } + + blob = bos.toByteArray(); + } + if (progressListener != null) { + progressListener.updateProgress(100); + } + + return blob; + } + + private Map makeRequestParams() { + return Map.of("from", apiIdentity, "secret", secret); + } + + private String doGet(URI uri, Map getParams) throws IOException { + var uriWithParameters = + Optional.ofNullable(getParams) + .map(params -> URI.create(uri.toString() + "?" + makeUrlEncoded(params))) + .orElse(uri); + + var httpRequest = HttpRequest.newBuilder().uri(uriWithParameters).GET().build(); + try { + HttpResponse response = httpClient.send(httpRequest, BodyHandlers.ofString()); + + var statusCode = response.statusCode(); + if (400 <= statusCode) { + throw new ThreemaError(response); + } + + return response.body(); + + } catch (InterruptedException e) { + log.warn("Interrupted"); + Thread.currentThread().interrupt(); + } + return null; + } + + private String doPost(URL url, Map postParams) throws IOException { + + var postData = makeUrlEncoded(postParams).getBytes(StandardCharsets.UTF_8); + + var connection = (HttpsURLConnection) url.openConnection(); + connection.setDoOutput(true); + connection.setDoInput(true); + connection.setInstanceFollowRedirects(false); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + connection.setRequestProperty("Charset", "utf-8"); + connection.setRequestProperty("Content-Length", Integer.toString(postData.length)); + connection.setUseCaches(false); + + var os = connection.getOutputStream(); + os.write(postData); + os.flush(); + os.close(); + + var is = connection.getInputStream(); + var br = new BufferedReader(new InputStreamReader(is)); + var response = br.readLine(); + br.close(); + + connection.disconnect(); + + return response; + } + + private String makeUrlEncoded(Map params) { + var s = new StringBuilder(); + + for (Map.Entry param : params.entrySet()) { + if (s.length() > 0) { + s.append('&'); + } + + s.append(param.getKey()); + s.append('='); + try { + s.append(URLEncoder.encode(param.getValue(), "UTF-8")); + } catch (UnsupportedEncodingException ignored) { + // ignored + } + } + + return s.toString(); + } +} diff --git a/api/src/main/java/net/klesatschke/threema/api/CryptTool.java b/api/src/main/java/net/klesatschke/threema/api/CryptTool.java new file mode 100644 index 0000000..a5ea37b --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/CryptTool.java @@ -0,0 +1,474 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.LinkedList; +import java.util.List; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.io.EndianUtils; + +import com.neilalexander.jnacl.NaCl; + +import net.klesatschke.threema.api.exceptions.BadMessageException; +import net.klesatschke.threema.api.exceptions.DecryptionFailedException; +import net.klesatschke.threema.api.exceptions.MessageParseException; +import net.klesatschke.threema.api.exceptions.UnsupportedMessageTypeException; +import net.klesatschke.threema.api.messages.DeliveryReceipt; +import net.klesatschke.threema.api.messages.FileMessage; +import net.klesatschke.threema.api.messages.ImageMessage; +import net.klesatschke.threema.api.messages.TextMessage; +import net.klesatschke.threema.api.messages.ThreemaMessage; +import net.klesatschke.threema.api.results.EncryptResult; +import net.klesatschke.threema.api.results.UploadResult; + +/** Contains static methods to do various Threema cryptography related tasks. */ +public class CryptTool { + private static final String HMAC_SHA256 = "HmacSHA256"; + + private CryptTool() {} + + /* HMAC-SHA256 keys for email/mobile phone hashing */ + private static final byte[] EMAIL_HMAC_KEY = { + (byte) 0x30, + (byte) 0xa5, + (byte) 0x50, + (byte) 0x0f, + (byte) 0xed, + (byte) 0x97, + (byte) 0x01, + (byte) 0xfa, + (byte) 0x6d, + (byte) 0xef, + (byte) 0xdb, + (byte) 0x61, + (byte) 0x08, + (byte) 0x41, + (byte) 0x90, + (byte) 0x0f, + (byte) 0xeb, + (byte) 0xb8, + (byte) 0xe4, + (byte) 0x30, + (byte) 0x88, + (byte) 0x1f, + (byte) 0x7a, + (byte) 0xd8, + (byte) 0x16, + (byte) 0x82, + (byte) 0x62, + (byte) 0x64, + (byte) 0xec, + (byte) 0x09, + (byte) 0xba, + (byte) 0xd7 + }; + private static final byte[] PHONENO_HMAC_KEY = { + (byte) 0x85, + (byte) 0xad, + (byte) 0xf8, + (byte) 0x22, + (byte) 0x69, + (byte) 0x53, + (byte) 0xf3, + (byte) 0xd9, + (byte) 0x6c, + (byte) 0xfd, + (byte) 0x5d, + (byte) 0x09, + (byte) 0xbf, + (byte) 0x29, + (byte) 0x55, + (byte) 0x5e, + (byte) 0xb9, + (byte) 0x55, + (byte) 0xfc, + (byte) 0xd8, + (byte) 0xaa, + (byte) 0x5e, + (byte) 0xc4, + (byte) 0xf9, + (byte) 0xfc, + (byte) 0xd8, + (byte) 0x69, + (byte) 0xe2, + (byte) 0x58, + (byte) 0x37, + (byte) 0x07, + (byte) 0x23 + }; + + private static final byte[] FILE_NONCE = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + private static final byte[] FILE_THUMBNAIL_NONCE = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 + }; + + private static final SecureRandom random = new SecureRandom(); + + /** + * Encrypt a text message. + * + * @param text the text to be encrypted (max. 3500 bytes) + * @param senderPrivateKey the private key of the sending ID + * @param recipientPublicKey the public key of the receiving ID + * @return encrypted result + */ + public static EncryptResult encryptTextMessage( + String text, byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new TextMessage(text), senderPrivateKey, recipientPublicKey); + } + + /** + * Encrypt an image message. + * + * @param encryptResult result of the image encryption + * @param uploadResult result of the upload + * @param senderPrivateKey the private key of the sending ID + * @param recipientPublicKey the public key of the receiving ID + * @return encrypted result + */ + public static EncryptResult encryptImageMessage( + EncryptResult encryptResult, + UploadResult uploadResult, + byte[] senderPrivateKey, + byte[] recipientPublicKey) { + return encryptMessage( + new ImageMessage( + uploadResult.getBlobId(), encryptResult.getSize(), encryptResult.getNonce()), + senderPrivateKey, + recipientPublicKey); + } + + /** + * Encrypt a file message. + * + * @param encryptResult result of the file data encryption + * @param uploadResult result of the upload + * @param mimeType MIME type of the file + * @param fileName File name + * @param fileSize Size of the file, in bytes + * @param uploadResultThumbnail result of thumbnail upload + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the file message encryption (not the same as the file data encryption!) + */ + public static EncryptResult encryptFileMessage( + EncryptResult encryptResult, + UploadResult uploadResult, + String mimeType, + String fileName, + int fileSize, + UploadResult uploadResultThumbnail, + byte[] senderPrivateKey, + byte[] recipientPublicKey) { + return encryptMessage( + new FileMessage( + uploadResult.getBlobId(), + encryptResult.getSecret(), + mimeType, + fileName, + fileSize, + uploadResultThumbnail != null ? uploadResultThumbnail.getBlobId() : null), + senderPrivateKey, + recipientPublicKey); + } + + private static EncryptResult encryptMessage( + ThreemaMessage threemaMessage, byte[] privateKey, byte[] publicKey) { + /* determine random amount of PKCS7 padding */ + var padbytes = random.nextInt(254) + 1; + + byte[] messageBytes; + try { + messageBytes = threemaMessage.getData(); + } catch (BadMessageException e) { + return null; + } + + /* prepend type byte (0x02) to message data */ + var data = new byte[1 + messageBytes.length + padbytes]; + data[0] = (byte) threemaMessage.getTypeCode(); + + System.arraycopy(messageBytes, 0, data, 1, messageBytes.length); + + /* append padding */ + for (var i = 0; i < padbytes; i++) { + data[i + 1 + messageBytes.length] = (byte) padbytes; + } + + return encrypt(data, privateKey, publicKey); + } + + /** + * Decrypt an NaCl box using the recipient's private key and the sender's public key. + * + * @param box The box to be decrypted + * @param privateKey The private key of the recipient + * @param publicKey The public key of the sender + * @param nonce The nonce that was used for encryption + * @return The decrypted data, or null if decryption failed + */ + public static byte[] decrypt(byte[] box, byte[] privateKey, byte[] publicKey, byte[] nonce) { + return new NaCl(privateKey, publicKey).decrypt(box, nonce); + } + + /** + * Decrypt symmetrically encrypted file data. + * + * @param fileData The encrypted file data + * @param secret The symmetric key that was used for encryption + * @return The decrypted file data, or null if decryption failed + */ + public static byte[] decryptFileData(byte[] fileData, byte[] secret) { + return NaCl.symmetricDecryptData(fileData, secret, FILE_NONCE); + } + + /** + * Decrypt symmetrically encrypted file thumbnail data. + * + * @param fileData The encrypted thumbnail data + * @param secret The symmetric key that was used for encryption + * @return The decrypted thumbnail data, or null if decryption failed + */ + public static byte[] decryptFileThumbnailData(byte[] fileData, byte[] secret) { + return NaCl.symmetricDecryptData(fileData, secret, FILE_THUMBNAIL_NONCE); + } + + /** + * Decrypt a message. + * + * @param box the box to be decrypted + * @param recipientPrivateKey the private key of the receiving ID + * @param senderPublicKey the public key of the sending ID + * @param nonce the nonce that was used for the encryption + * @return decrypted message (text or delivery receipt) + */ + public static ThreemaMessage decryptMessage( + byte[] box, byte[] recipientPrivateKey, byte[] senderPublicKey, byte[] nonce) + throws MessageParseException { + + var data = decrypt(box, recipientPrivateKey, senderPublicKey, nonce); + if (data == null) { + throw new DecryptionFailedException(); + } + + /* remove padding */ + var padbytes = data[data.length - 1] & 0xFF; + var realDataLength = data.length - padbytes; + if (realDataLength < 1) { + throw new BadMessageException(); /* Bad message padding */ + } + + /* first byte of data is type */ + var type = data[0] & 0xFF; + + switch (type) { + case TextMessage.TYPE_CODE: + /* Text message */ + if (realDataLength < 2) { + throw new BadMessageException(); + } + + return new TextMessage(new String(data, 1, realDataLength - 1, StandardCharsets.UTF_8)); + + case DeliveryReceipt.TYPE_CODE: + /* Delivery receipt */ + if (realDataLength < MessageId.MESSAGE_ID_LEN + 2 + || (realDataLength - 2) % MessageId.MESSAGE_ID_LEN != 0) { + throw new BadMessageException(); + } + + var receiptType = DeliveryReceipt.Type.get(data[1] & 0xFF); + if (receiptType == null) { + throw new BadMessageException(); + } + + List messageIds = new LinkedList<>(); + + var numMsgIds = (realDataLength - 2) / MessageId.MESSAGE_ID_LEN; + for (var i = 0; i < numMsgIds; i++) { + messageIds.add(new MessageId(data, 2 + i * MessageId.MESSAGE_ID_LEN)); + } + + return new DeliveryReceipt(receiptType, messageIds); + + case ImageMessage.TYPE_CODE: + if (realDataLength != 1 + ThreemaMessage.BLOB_ID_LEN + 4 + NaCl.NONCEBYTES) { + System.out.println(String.valueOf(realDataLength)); + System.out.println(String.valueOf(1 + ThreemaMessage.BLOB_ID_LEN + 4 + NaCl.NONCEBYTES)); + throw new BadMessageException(); + } + var blobId = new byte[ThreemaMessage.BLOB_ID_LEN]; + System.arraycopy(data, 1, blobId, 0, ThreemaMessage.BLOB_ID_LEN); + var size = EndianUtils.readSwappedInteger(data, 1 + ThreemaMessage.BLOB_ID_LEN); + var fileNonce = new byte[NaCl.NONCEBYTES]; + System.arraycopy(data, 1 + 4 + ThreemaMessage.BLOB_ID_LEN, fileNonce, 0, nonce.length); + + return new ImageMessage(blobId, size, fileNonce); + + case FileMessage.TYPE_CODE: + try { + return FileMessage.fromString(new String(data, 1, realDataLength - 1, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new BadMessageException(); + } + + default: + throw new UnsupportedMessageTypeException(); + } + } + + /** + * Generate a new key pair. + * + * @param privateKey is used to return the generated private key (length must be + * NaCl.PRIVATEKEYBYTES) + * @param publicKey is used to return the generated public key (length must be + * NaCl.PUBLICKEYBYTES) + */ + public static void generateKeyPair(byte[] privateKey, byte[] publicKey) { + if (publicKey.length != NaCl.PUBLICKEYBYTES || privateKey.length != NaCl.SECRETKEYBYTES) { + throw new IllegalArgumentException("Wrong key length"); + } + + NaCl.genkeypair(publicKey, privateKey); + } + + /** + * Encrypt data using NaCl asymmetric ("box") encryption. + * + * @param data the data to be encrypted + * @param privateKey is used to return the generated private key (length must be + * NaCl.PRIVATEKEYBYTES) + * @param publicKey is used to return the generated public key (length must be + * NaCl.PUBLICKEYBYTES) + */ + public static EncryptResult encrypt(byte[] data, byte[] privateKey, byte[] publicKey) { + if (publicKey.length != NaCl.PUBLICKEYBYTES || privateKey.length != NaCl.SECRETKEYBYTES) { + throw new IllegalArgumentException("Wrong key length"); + } + + var nonce = randomNonce(); + var naCl = new NaCl(privateKey, publicKey); + return new EncryptResult(naCl.encrypt(data, nonce), null, nonce); + } + + /** + * Encrypt file data using NaCl symmetric encryption with a random key. + * + * @param data the file contents to be encrypted + * @return the encryption result including the random key + */ + public static EncryptResult encryptFileData(byte[] data) { + // create random key + var rnd = new SecureRandom(); + var encryptionKey = new byte[NaCl.SYMMKEYBYTES]; + rnd.nextBytes(encryptionKey); + + // encrypt file data in-place + NaCl.symmetricEncryptDataInplace(data, encryptionKey, FILE_NONCE); + + return new EncryptResult(data, encryptionKey, FILE_NONCE); + } + + /** + * Encrypt file thumbnail data using NaCl symmetric encryption with a random key. + * + * @param data the file contents to be encrypted + * @return the encryption result including the random key + */ + public static EncryptResult encryptFileThumbnailData(byte[] data, byte[] encryptionKey) { + // encrypt file data in-place + NaCl.symmetricEncryptDataInplace(data, encryptionKey, FILE_THUMBNAIL_NONCE); + + return new EncryptResult(data, encryptionKey, FILE_THUMBNAIL_NONCE); + } + + /** + * Hashes an email address for identity lookup. + * + * @param email the email address + * @return the raw hash + */ + public static byte[] hashEmail(String email) { + try { + var emailMac = Mac.getInstance(HMAC_SHA256); + emailMac.init(new SecretKeySpec(EMAIL_HMAC_KEY, HMAC_SHA256)); + var normalizedEmail = email.toLowerCase().trim(); + return emailMac.doFinal(normalizedEmail.getBytes("US-ASCII")); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Hashes a phone number for identity lookup. + * + * @param phoneNo the phone number + * @return the raw hash + */ + public static byte[] hashPhoneNo(String phoneNo) { + try { + var phoneMac = Mac.getInstance(HMAC_SHA256); + phoneMac.init(new SecretKeySpec(PHONENO_HMAC_KEY, HMAC_SHA256)); + var normalizedPhoneNo = phoneNo.replaceAll("[^0-9]", ""); + return phoneMac.doFinal(normalizedPhoneNo.getBytes("US-ASCII")); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Generate a random nonce. + * + * @return random nonce + */ + public static byte[] randomNonce() { + var nonce = new byte[NaCl.NONCEBYTES]; + random.nextBytes(nonce); + return nonce; + } + + /** + * Return the public key that corresponds with a given private key. + * + * @param privateKey The private key whose public key should be derived + * @return The corresponding public key. + */ + public static byte[] derivePublicKey(byte[] privateKey) { + return NaCl.derivePublicKey(privateKey); + } +} diff --git a/api/src/main/java/net/klesatschke/threema/api/DataUtils.java b/api/src/main/java/net/klesatschke/threema/api/DataUtils.java new file mode 100644 index 0000000..be992b8 --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/DataUtils.java @@ -0,0 +1,142 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +import net.klesatschke.threema.api.Key.KeyType; +import net.klesatschke.threema.api.exceptions.InvalidKeyException; + +public class DataUtils { + private DataUtils() {} + + /** + * Convert a string in hexadecimal representation to a byte array. + * + * @param s hex string + * @return decoded byte array + */ + public static byte[] hexStringToByteArray(String s) { + var sc = s.replaceAll("[^0-9a-fA-F]", ""); + var len = sc.length(); + var data = new byte[len / 2]; + for (var i = 0; i < len; i += 2) { + data[i / 2] = + (byte) ((Character.digit(sc.charAt(i), 16) << 4) + Character.digit(sc.charAt(i + 1), 16)); + } + return data; + } + + /** + * Convert a byte array into a hexadecimal string (lowercase). + * + * @param bytes the bytes to encode + * @return hex encoded string + */ + public static String byteArrayToHexString(byte[] bytes) { + final char[] hexArray = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + var hexChars = new char[bytes.length * 2]; + int v; + for (var j = 0; j < bytes.length; j++) { + v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + /** + * Read hexadecimal data from a file and return it as a byte array. + * + * @param inFile input file + * @return the decoded data + * @throws java.io.IOException + */ + public static byte[] readHexFile(File inFile) throws IOException { + try (var br = new BufferedReader(new FileReader(inFile))) { + return hexStringToByteArray(br.readLine().trim()); + } + } + + /** + * Write a byte array into a file in hexadecimal format. + * + * @param outFile output file + * @param data the data to be written + */ + public static void writeHexFile(File outFile, byte[] data) throws IOException { + try (var fw = new FileWriter(outFile)) { + fw.write(byteArrayToHexString(data)); + fw.write('\n'); + } + } + + /** + * Read an encoded key from a file and return it as a key instance. + * + * @param inFile input file + * @return the decoded key + * @throws java.io.IOException + */ + public static Key readKeyFile(File inFile) throws IOException, InvalidKeyException { + try (var br = new BufferedReader(new FileReader(inFile))) { + return Key.decodeKey(br.readLine().trim()); + } + } + + /** + * Read an encoded key from a file and return it as a key instance. + * + * @param inFile input file + * @param expectedKeyType validates the key type (private or public) + * @return the decoded key + * @throws java.io.IOException + */ + public static Key readKeyFile(File inFile, KeyType expectedKeyType) + throws IOException, InvalidKeyException { + try (var br = new BufferedReader(new FileReader(inFile))) { + return Key.decodeKey(br.readLine().trim(), expectedKeyType); + } + } + + /** + * Write an encoded key to a file Encoded key format: type:hex_key. + * + * @param outFile output file + * @param key a key that will be encoded and written to a file + */ + public static void writeKeyFile(File outFile, Key key) throws IOException { + try (var fw = new FileWriter(outFile)) { + fw.write(key.encode()); + fw.write('\n'); + } + } +} diff --git a/api/src/main/java/net/klesatschke/threema/api/Key.java b/api/src/main/java/net/klesatschke/threema/api/Key.java new file mode 100644 index 0000000..faeda2c --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/Key.java @@ -0,0 +1,98 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2022 Kai Klesatschke + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api; + +import lombok.Value; +import net.klesatschke.threema.api.exceptions.InvalidKeyException; + +/** Encapsulates an asymmetric key, either public or private. */ +@Value +public class Key { + public static final String SEPARATOR = ":"; + + public enum KeyType { + PRIVATE, + PUBLIC; + } + + private final KeyType type; + private final byte[] key; + + /** + * Decodes and validates an encoded key. Encoded key format: type:hex_key + * + * @param encodedKey an encoded key + * @throws ch.threema.apitool.exceptions.InvalidKeyException + */ + public static Key decodeKey(String encodedKey) throws InvalidKeyException { + // Split key and check length + var keyArray = encodedKey.split(Key.SEPARATOR); + if (keyArray.length != 2) { + throw new InvalidKeyException("Does not contain a valid key format"); + } + + // Unpack key + var keyType = KeyType.valueOf(keyArray[0].toUpperCase()); + var keyContent = keyArray[1]; + + // Is this a valid hex key? + if (!keyContent.matches("[0-9a-fA-F]{64}")) { + throw new InvalidKeyException("Does not contain a valid key"); + } + + return new Key(keyType, DataUtils.hexStringToByteArray(keyContent)); + } + + /** + * Decodes and validates an encoded key. Encoded key format: type:hex_key + * + * @param encodedKey an encoded key + * @param expectedKeyType the expected type of the key + * @throws InvalidKeyException + */ + public static Key decodeKey(String encodedKey, KeyType expectedKeyType) + throws InvalidKeyException { + var key = decodeKey(encodedKey); + + // Check key type + if (!key.type.equals(expectedKeyType)) { + throw new InvalidKeyException("Expected key type: " + expectedKeyType + ", got: " + key.type); + } + + return key; + } + + /** + * Encodes a key. + * + * @return an encoded key + */ + public String encode() { + return this.type.toString().toLowerCase() + + Key.SEPARATOR + + DataUtils.byteArrayToHexString(this.key); + } +} diff --git a/source/src/main/java/ch/threema/apitool/MessageId.java b/api/src/main/java/net/klesatschke/threema/api/MessageId.java similarity index 58% rename from source/src/main/java/ch/threema/apitool/MessageId.java rename to api/src/main/java/net/klesatschke/threema/api/MessageId.java index 2c3d5b1..6cd3087 100644 --- a/source/src/main/java/ch/threema/apitool/MessageId.java +++ b/api/src/main/java/net/klesatschke/threema/api/MessageId.java @@ -22,38 +22,38 @@ * THE SOFTWARE */ -package ch.threema.apitool; +package net.klesatschke.threema.api; -/** - * Encapsulates the 8-byte message IDs that Threema uses. - */ +/** Encapsulates the 8-byte message IDs that Threema uses. */ public class MessageId { - public static final int MESSAGE_ID_LEN = 8; + public static final int MESSAGE_ID_LEN = 8; - private final byte[] messageId; + private final byte[] messageId; - public MessageId(byte[] messageId) { - if (messageId.length != MESSAGE_ID_LEN) - throw new IllegalArgumentException("Bad message ID length"); + public MessageId(byte[] messageId) { + if (messageId.length != MESSAGE_ID_LEN) { + throw new IllegalArgumentException("Bad message ID length"); + } - this.messageId = messageId; - } + this.messageId = messageId; + } - public MessageId(byte[] data, int offset) { - if ((offset + MESSAGE_ID_LEN) > data.length) - throw new IllegalArgumentException("Bad message ID buffer length"); + public MessageId(byte[] data, int offset) { + if (offset + MESSAGE_ID_LEN > data.length) { + throw new IllegalArgumentException("Bad message ID buffer length"); + } - this.messageId = new byte[MESSAGE_ID_LEN]; - System.arraycopy(data, offset, this.messageId, 0, MESSAGE_ID_LEN); - } + this.messageId = new byte[MESSAGE_ID_LEN]; + System.arraycopy(data, offset, this.messageId, 0, MESSAGE_ID_LEN); + } - public byte[] getMessageId() { - return messageId; - } + public byte[] getMessageId() { + return messageId; + } - @Override - public String toString() { - return DataUtils.byteArrayToHexString(messageId); - } + @Override + public String toString() { + return DataUtils.byteArrayToHexString(messageId); + } } diff --git a/api/src/main/java/net/klesatschke/threema/api/PublicKeyStore.java b/api/src/main/java/net/klesatschke/threema/api/PublicKeyStore.java new file mode 100644 index 0000000..f155288 --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/PublicKeyStore.java @@ -0,0 +1,85 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2022 Kai Klesatschke + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Stores and caches public keys for Threema users. Extend this class to provide your own storage + * implementation, e.g. in a file or database. + */ +public abstract class PublicKeyStore { + private final Map cache = new ConcurrentHashMap<>(); + + /** + * Get the public key for a given Threema ID. The cache is checked first; if it is not found in + * the cache, fetchPublicKey() is called. + * + * @param threemaId The Threema ID whose public key should be obtained + * @return The public key, or null if not found. + */ + public final byte[] getPublicKey(String threemaId) { + return this.cache.computeIfAbsent(threemaId, this::fetchPublicKey); + } + + /** + * Store the public key for a given Threema ID in the cache, and the underlying store. + * + * @param threemaId The Threema ID whose public key should be stored + * @param publicKey The corresponding public key. + */ + public final void setPublicKey(String threemaId, byte[] publicKey) { + Optional.ofNullable(publicKey) + .ifPresent( + key -> + cache.compute( + threemaId, + (id, old) -> { + save(id, key); + return key; + })); + } + + /** + * Fetch the public key for the given Threema ID from the store. Override to provide your own + * implementation to read from the store. + * + * @param threemaId The Threema ID whose public key should be obtained + * @return The public key, or null if not found. + */ + protected abstract byte[] fetchPublicKey(String threemaId); + + /** + * Save the public key for a given Threema ID in the store. Override to provide your own + * implementation to write to the store. + * + * @param threemaId The Threema ID whose public key should be stored + * @param publicKey The corresponding public key. + */ + protected abstract void save(String threemaId, byte[] publicKey); +} diff --git a/source/src/main/java/ch/threema/apitool/exceptions/BadMessageException.java b/api/src/main/java/net/klesatschke/threema/api/exceptions/BadMessageException.java similarity index 84% rename from source/src/main/java/ch/threema/apitool/exceptions/BadMessageException.java rename to api/src/main/java/net/klesatschke/threema/api/exceptions/BadMessageException.java index 1b4099d..265d952 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/BadMessageException.java +++ b/api/src/main/java/net/klesatschke/threema/api/exceptions/BadMessageException.java @@ -22,12 +22,10 @@ * THE SOFTWARE */ -package ch.threema.apitool.exceptions; +package net.klesatschke.threema.api.exceptions; -/** - * Exception that gets thrown if a message has a bad/illegal format after it has been decrypted. - */ +/** Exception that gets thrown if a message has a bad/illegal format after it has been decrypted. */ public class BadMessageException extends MessageParseException { - private static final long serialVersionUID = 4812096297596964107L; + private static final long serialVersionUID = 4812096297596964107L; } diff --git a/api/src/main/java/net/klesatschke/threema/api/exceptions/ClientError.java b/api/src/main/java/net/klesatschke/threema/api/exceptions/ClientError.java new file mode 100644 index 0000000..a2c1bd5 --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/exceptions/ClientError.java @@ -0,0 +1,11 @@ +package net.klesatschke.threema.api.exceptions; + +import java.net.http.HttpResponse; + +public class ClientError extends ThreemaError { + private static final long serialVersionUID = 5586830942850922017L; + + public ClientError(HttpResponse response, String string) { + super(response, string); + } +} diff --git a/source/src/main/java/ch/threema/apitool/exceptions/DecryptionFailedException.java b/api/src/main/java/net/klesatschke/threema/api/exceptions/DecryptionFailedException.java similarity index 88% rename from source/src/main/java/ch/threema/apitool/exceptions/DecryptionFailedException.java rename to api/src/main/java/net/klesatschke/threema/api/exceptions/DecryptionFailedException.java index 1992d37..83aacdc 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/DecryptionFailedException.java +++ b/api/src/main/java/net/klesatschke/threema/api/exceptions/DecryptionFailedException.java @@ -22,12 +22,13 @@ * THE SOFTWARE */ -package ch.threema.apitool.exceptions; +package net.klesatschke.threema.api.exceptions; /** - * Exception that gets thrown when decryption fails (because the keys are incorrect, or the data is corrupted). + * Exception that gets thrown when decryption fails (because the keys are incorrect, or the data is + * corrupted). */ public class DecryptionFailedException extends MessageParseException { - private static final long serialVersionUID = 2523453399446307538L; + private static final long serialVersionUID = 2523453399446307538L; } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/InvalidKeyException.java b/api/src/main/java/net/klesatschke/threema/api/exceptions/InvalidKeyException.java similarity index 81% rename from source/src/main/java/ch/threema/apitool/exceptions/InvalidKeyException.java rename to api/src/main/java/net/klesatschke/threema/api/exceptions/InvalidKeyException.java index f213b55..d23f1c4 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/InvalidKeyException.java +++ b/api/src/main/java/net/klesatschke/threema/api/exceptions/InvalidKeyException.java @@ -22,15 +22,13 @@ * THE SOFTWARE */ -package ch.threema.apitool.exceptions; +package net.klesatschke.threema.api.exceptions; -/** - * Exception that gets thrown when an invalid key has been specified (e.g. wrong length). - */ +/** Exception that gets thrown when an invalid key has been specified (e.g. wrong length). */ public class InvalidKeyException extends Exception { - private static final long serialVersionUID = 7585373757748175309L; + private static final long serialVersionUID = 7585373757748175309L; - public InvalidKeyException(String s) { - super(s); - } + public InvalidKeyException(String s) { + super(s); + } } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/MessageParseException.java b/api/src/main/java/net/klesatschke/threema/api/exceptions/MessageParseException.java similarity index 91% rename from source/src/main/java/ch/threema/apitool/exceptions/MessageParseException.java rename to api/src/main/java/net/klesatschke/threema/api/exceptions/MessageParseException.java index aaed853..35a9570 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/MessageParseException.java +++ b/api/src/main/java/net/klesatschke/threema/api/exceptions/MessageParseException.java @@ -22,12 +22,12 @@ * THE SOFTWARE */ -package ch.threema.apitool.exceptions; +package net.klesatschke.threema.api.exceptions; /** * Base class for exceptions that may occur when parsing/decrypting an encrypted Threema message. */ public class MessageParseException extends Exception { - private static final long serialVersionUID = 6829629439344637547L; + private static final long serialVersionUID = 6829629439344637547L; } diff --git a/api/src/main/java/net/klesatschke/threema/api/exceptions/ServerError.java b/api/src/main/java/net/klesatschke/threema/api/exceptions/ServerError.java new file mode 100644 index 0000000..f78abd8 --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/exceptions/ServerError.java @@ -0,0 +1,16 @@ +package net.klesatschke.threema.api.exceptions; + +import java.net.http.HttpResponse; + +public class ServerError extends ThreemaError { + + private static final long serialVersionUID = 1306889474607193102L; + + public ServerError(HttpResponse response) { + super(response); + } + + public ServerError(HttpResponse response, String string) { + super(response, string); + } +} diff --git a/api/src/main/java/net/klesatschke/threema/api/exceptions/ThreemaError.java b/api/src/main/java/net/klesatschke/threema/api/exceptions/ThreemaError.java new file mode 100644 index 0000000..f621f6d --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/exceptions/ThreemaError.java @@ -0,0 +1,18 @@ +package net.klesatschke.threema.api.exceptions; + +import java.io.IOException; +import java.net.http.HttpResponse; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class ThreemaError extends IOException { + private static final long serialVersionUID = -5089520812601719876L; + @Getter private final HttpResponse response; + + public ThreemaError(HttpResponse response, String string) { + super(string); + this.response = response; + } +} diff --git a/source/src/main/java/ch/threema/apitool/exceptions/UnsupportedMessageTypeException.java b/api/src/main/java/net/klesatschke/threema/api/exceptions/UnsupportedMessageTypeException.java similarity index 87% rename from source/src/main/java/ch/threema/apitool/exceptions/UnsupportedMessageTypeException.java rename to api/src/main/java/net/klesatschke/threema/api/exceptions/UnsupportedMessageTypeException.java index 76c9260..d6804b8 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/UnsupportedMessageTypeException.java +++ b/api/src/main/java/net/klesatschke/threema/api/exceptions/UnsupportedMessageTypeException.java @@ -22,13 +22,13 @@ * THE SOFTWARE */ -package ch.threema.apitool.exceptions; +package net.klesatschke.threema.api.exceptions; /** - * Exception that gets thrown when an attempt has been made to decrypt a message - * of a type that is not supported by this library. + * Exception that gets thrown when an attempt has been made to decrypt a message of a type that is + * not supported by this library. */ public class UnsupportedMessageTypeException extends MessageParseException { - private static final long serialVersionUID = -686063411249892256L; + private static final long serialVersionUID = -686063411249892256L; } diff --git a/api/src/main/java/net/klesatschke/threema/api/messages/DeliveryReceipt.java b/api/src/main/java/net/klesatschke/threema/api/messages/DeliveryReceipt.java new file mode 100644 index 0000000..a792b8b --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/messages/DeliveryReceipt.java @@ -0,0 +1,117 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api.messages; + +import java.util.List; + +import net.klesatschke.threema.api.MessageId; + +/** + * A delivery receipt message that can be sent/received with end-to-end encryption via Threema. Each + * delivery receipt message confirms the receipt of one or multiple regular messages. + */ +public class DeliveryReceipt extends ThreemaMessage { + + public static final int TYPE_CODE = 0x80; + + private final Type receiptType; + private final List ackedMessageIds; + + public DeliveryReceipt(Type receiptType, List ackedMessageIds) { + this.receiptType = receiptType; + this.ackedMessageIds = ackedMessageIds; + } + + public Type getReceiptType() { + return receiptType; + } + + public List getAckedMessageIds() { + return ackedMessageIds; + } + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public String toString() { + var sb = new StringBuilder("Delivery receipt ("); + sb.append(receiptType); + sb.append("): "); + var i = 0; + for (MessageId messageId : ackedMessageIds) { + if (i != 0) { + sb.append(", "); + } + sb.append(messageId); + i++; + } + return sb.toString(); + } + + /** + * A delivery receipt type. The following types are defined: + * + *
    + *
  • RECEIVED: the message has been received and decrypted on the recipient's device + *
  • READ: the message has been shown to the user in the chat view (note that this status can + * be disabled) + *
  • USER_ACK: the user has explicitly acknowledged the message (usually by long-pressing it + * and choosing the "acknowledge" option) + *
+ */ + public enum Type { + RECEIVED(1), + READ(2), + USER_ACK(3); + + private final int code; + + Type(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static Type get(int code) { + for (Type t : values()) { + if (t.code == code) { + return t; + } + } + return null; + } + } + + @Override + public byte[] getData() { + // Not implemented yet + return new byte[0]; + } +} diff --git a/api/src/main/java/net/klesatschke/threema/api/messages/FileMessage.java b/api/src/main/java/net/klesatschke/threema/api/messages/FileMessage.java new file mode 100644 index 0000000..2d7a736 --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/messages/FileMessage.java @@ -0,0 +1,159 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api.messages; + +import java.io.UnsupportedEncodingException; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; + +import net.klesatschke.threema.api.DataUtils; +import net.klesatschke.threema.api.exceptions.BadMessageException; + +/** A file message that can be sent/received with end-to-end encryption via Threema. */ +public class FileMessage extends ThreemaMessage { + private static final String KEY_BLOB_ID = "b"; + private static final String KEY_THUMBNAIL_BLOB_ID = "t"; + private static final String KEY_ENCRYPTION_KEY = "k"; + private static final String KEY_MIME_TYPE = "m"; + private static final String KEY_FILE_NAME = "n"; + private static final String KEY_FILE_SIZE = "s"; + private static final String KEY_TYPE = "i"; + + public static final int TYPE_CODE = 0x17; + + private final byte[] blobId; + private final byte[] encryptionKey; + private final String mimeType; + private final String fileName; + private final int fileSize; + private final byte[] thumbnailBlobId; + + public FileMessage( + byte[] blobId, + byte[] encryptionKey, + String mimeType, + String fileName, + int fileSize, + byte[] thumbnailBlobId) { + this.blobId = blobId; + this.encryptionKey = encryptionKey; + this.mimeType = mimeType; + this.fileName = fileName; + this.fileSize = fileSize; + this.thumbnailBlobId = thumbnailBlobId; + } + + public byte[] getBlobId() { + return this.blobId; + } + + public byte[] getEncryptionKey() { + return this.encryptionKey; + } + + public String getMimeType() { + return this.mimeType; + } + + public String getFileName() { + return this.fileName; + } + + public int getFileSize() { + return this.fileSize; + } + + public byte[] getThumbnailBlobId() { + return this.thumbnailBlobId; + } + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public String toString() { + return "file message " + this.fileName; + } + + @Override + public byte[] getData() throws BadMessageException { + var o = new JsonObject(); + try { + o.addProperty(KEY_BLOB_ID, DataUtils.byteArrayToHexString(this.blobId)); + o.addProperty( + KEY_THUMBNAIL_BLOB_ID, + this.thumbnailBlobId != null + ? DataUtils.byteArrayToHexString(this.thumbnailBlobId) + : null); + o.addProperty(KEY_ENCRYPTION_KEY, DataUtils.byteArrayToHexString(this.encryptionKey)); + o.addProperty(KEY_MIME_TYPE, this.mimeType); + o.addProperty(KEY_FILE_NAME, this.fileName); + o.addProperty(KEY_FILE_SIZE, this.fileSize); + o.addProperty(KEY_TYPE, 0); + } catch (Exception e) { + throw new BadMessageException(); + } + + try { + return o.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + public static FileMessage fromString(String json) throws BadMessageException { + try { + var o = new Gson().fromJson(json, JsonObject.class); + byte[] encryptionKey = + DataUtils.hexStringToByteArray(o.get(KEY_ENCRYPTION_KEY).getAsString()); + var mimeType = o.get(KEY_MIME_TYPE).getAsString(); + var fileSize = o.get(KEY_FILE_SIZE).getAsInt(); + byte[] blobId = DataUtils.hexStringToByteArray(o.get(KEY_BLOB_ID).getAsString()); + + String fileName; + byte[] thumbnailBlobId = null; + + // optional field + if (o.has(KEY_THUMBNAIL_BLOB_ID)) { + thumbnailBlobId = + DataUtils.hexStringToByteArray(o.get(KEY_THUMBNAIL_BLOB_ID).getAsString()); + } + + if (o.has(KEY_FILE_NAME)) { + fileName = o.get(KEY_FILE_NAME).getAsString(); + } else { + fileName = "unnamed"; + } + + return new FileMessage(blobId, encryptionKey, mimeType, fileName, fileSize, thumbnailBlobId); + } catch (JsonSyntaxException e) { + throw new BadMessageException(); + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/ImageMessage.java b/api/src/main/java/net/klesatschke/threema/api/messages/ImageMessage.java similarity index 52% rename from source/src/main/java/ch/threema/apitool/messages/ImageMessage.java rename to api/src/main/java/net/klesatschke/threema/api/messages/ImageMessage.java index 40e6369..958fba6 100644 --- a/source/src/main/java/ch/threema/apitool/messages/ImageMessage.java +++ b/api/src/main/java/net/klesatschke/threema/api/messages/ImageMessage.java @@ -22,66 +22,62 @@ * THE SOFTWARE */ -package ch.threema.apitool.messages; +package net.klesatschke.threema.api.messages; -import ch.threema.apitool.DataUtils; -import com.neilalexander.jnacl.NaCl; import org.apache.commons.io.EndianUtils; -/** - * An image message that can be sent/received with end-to-end encryption via Threema. - */ -public class ImageMessage extends ThreemaMessage { - - public static final int TYPE_CODE = 0x02; - private final byte[] blobId; - private final int size; - private final byte[] nonce; - - - public ImageMessage(byte[] blobId, int size, byte[] nonce) { - - this.blobId = blobId; - this.size = size; - this.nonce = nonce; - } - - public byte[] getBlobId() { - return this.blobId; - } - - - public int getSize() { - return this.size; - } - - - public byte[] getNonce() { - return this.nonce; - } - - @Override - public int getTypeCode() { - return TYPE_CODE; - } - - @Override - public String toString() { - return "blob " + DataUtils.byteArrayToHexString(this.blobId); - } - - @Override - public byte[] getData() { - byte[] data = new byte[BLOB_ID_LEN + 4 + NaCl.NONCEBYTES]; - int pos = 0; - System.arraycopy(this.blobId, 0, data, pos, BLOB_ID_LEN); - pos += BLOB_ID_LEN; +import com.neilalexander.jnacl.NaCl; - EndianUtils.writeSwappedInteger(data, pos, this.size); - pos += 4; +import net.klesatschke.threema.api.DataUtils; - System.arraycopy(this.nonce, 0, data, pos, NaCl.NONCEBYTES); - return data; +/** An image message that can be sent/received with end-to-end encryption via Threema. */ +public class ImageMessage extends ThreemaMessage { - } + public static final int TYPE_CODE = 0x02; + private final byte[] blobId; + private final int size; + private final byte[] nonce; + + public ImageMessage(byte[] blobId, int size, byte[] nonce) { + + this.blobId = blobId; + this.size = size; + this.nonce = nonce; + } + + public byte[] getBlobId() { + return this.blobId; + } + + public int getSize() { + return this.size; + } + + public byte[] getNonce() { + return this.nonce; + } + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public String toString() { + return "blob " + DataUtils.byteArrayToHexString(this.blobId); + } + + @Override + public byte[] getData() { + var data = new byte[BLOB_ID_LEN + 4 + NaCl.NONCEBYTES]; + var pos = 0; + System.arraycopy(this.blobId, 0, data, pos, BLOB_ID_LEN); + pos += BLOB_ID_LEN; + + EndianUtils.writeSwappedInteger(data, pos, this.size); + pos += 4; + + System.arraycopy(this.nonce, 0, data, pos, NaCl.NONCEBYTES); + return data; + } } diff --git a/source/src/main/java/ch/threema/apitool/messages/TextMessage.java b/api/src/main/java/net/klesatschke/threema/api/messages/TextMessage.java similarity index 67% rename from source/src/main/java/ch/threema/apitool/messages/TextMessage.java rename to api/src/main/java/net/klesatschke/threema/api/messages/TextMessage.java index 3a22e06..7a3e8fc 100644 --- a/source/src/main/java/ch/threema/apitool/messages/TextMessage.java +++ b/api/src/main/java/net/klesatschke/threema/api/messages/TextMessage.java @@ -22,43 +22,41 @@ * THE SOFTWARE */ -package ch.threema.apitool.messages; +package net.klesatschke.threema.api.messages; import java.io.UnsupportedEncodingException; -/** - * A text message that can be sent/received with end-to-end encryption via Threema. - */ +/** A text message that can be sent/received with end-to-end encryption via Threema. */ public class TextMessage extends ThreemaMessage { - public static final int TYPE_CODE = 0x01; + public static final int TYPE_CODE = 0x01; - private final String text; + private final String text; - public TextMessage(String text) { - this.text = text; - } + public TextMessage(String text) { + this.text = text; + } - public String getText() { - return text; - } + public String getText() { + return text; + } - @Override - public int getTypeCode() { - return TYPE_CODE; - } + @Override + public int getTypeCode() { + return TYPE_CODE; + } - @Override - public String toString() { - return text; - } + @Override + public String toString() { + return text; + } - @Override - public byte[] getData() { - try { - return text.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - return null; - } - } + @Override + public byte[] getData() { + try { + return text.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + return null; + } + } } diff --git a/source/src/main/java/ch/threema/apitool/messages/ThreemaMessage.java b/api/src/main/java/net/klesatschke/threema/api/messages/ThreemaMessage.java similarity index 73% rename from source/src/main/java/ch/threema/apitool/messages/ThreemaMessage.java rename to api/src/main/java/net/klesatschke/threema/api/messages/ThreemaMessage.java index f96265b..e4131f9 100644 --- a/source/src/main/java/ch/threema/apitool/messages/ThreemaMessage.java +++ b/api/src/main/java/net/klesatschke/threema/api/messages/ThreemaMessage.java @@ -22,24 +22,18 @@ * THE SOFTWARE */ -package ch.threema.apitool.messages; +package net.klesatschke.threema.api.messages; -import ch.threema.apitool.exceptions.BadMessageException; +import net.klesatschke.threema.api.exceptions.BadMessageException; -/** - * Abstract base class of messages that can be sent with end-to-end encryption via Threema. - */ +/** Abstract base class of messages that can be sent with end-to-end encryption via Threema. */ public abstract class ThreemaMessage { - public static final int BLOB_ID_LEN = 16; + public static final int BLOB_ID_LEN = 16; - /** - * @return The message's raw content - */ - public abstract byte[] getData() throws BadMessageException; + /** @return The message's raw content */ + public abstract byte[] getData() throws BadMessageException; - /** - * @return the message's type code - */ - public abstract int getTypeCode(); + /** @return the message's type code */ + public abstract int getTypeCode(); } diff --git a/api/src/main/java/net/klesatschke/threema/api/results/CapabilityResult.java b/api/src/main/java/net/klesatschke/threema/api/results/CapabilityResult.java new file mode 100644 index 0000000..21cd88e --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/results/CapabilityResult.java @@ -0,0 +1,88 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api.results; + +import lombok.Value; + +/** Result of a capability lookup */ +@Value +public class CapabilityResult { + private final String key; + + /** capabilities as a string array. */ + private final String[] capabilities; + + /** Get all capabilities as a string array. */ + public String[] getCapabilities() { + return capabilities; + } + + /** Check whether the Threema ID can receive text */ + public boolean canText() { + return this.can("text"); + } + + /** Check whether the Threema ID can receive images */ + public boolean canImage() { + return this.can("image"); + } + + /** Check whether the Threema ID can receive videos */ + public boolean canVideo() { + return this.can("video"); + } + + /** Check whether the Threema ID can receive audio */ + public boolean canAudio() { + return this.can("audio"); + } + + /** Check whether the Threema ID can receive files */ + public boolean canFile() { + return this.can("file"); + } + + private boolean can(String key) { + for (String k : this.capabilities) { + if (k.equals(key)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + var b = new StringBuilder(); + b.append(this.key).append(": "); + for (var n = 0; n < this.capabilities.length; n++) { + if (n > 0) { + b.append(","); + } + b.append(this.capabilities[n]); + } + return b.toString(); + } +} diff --git a/api/src/main/java/net/klesatschke/threema/api/results/EncryptResult.java b/api/src/main/java/net/klesatschke/threema/api/results/EncryptResult.java new file mode 100644 index 0000000..25aa3a4 --- /dev/null +++ b/api/src/main/java/net/klesatschke/threema/api/results/EncryptResult.java @@ -0,0 +1,43 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api.results; + +import lombok.Value; + +/** Result of a data encryption */ +@Value +public class EncryptResult { + /** the encrypted data */ + private final byte[] result; + /** the secret that was used for encryption (only for symmetric encryption, e.g. files) */ + private final byte[] secret; + /** the nonce that was used for encryption */ + private final byte[] nonce; + + /** @return the size (in bytes) of the encrypted data */ + public int getSize() { + return this.result.length; + } +} diff --git a/source/src/test/java/ch/threema/apitool/Assert.java b/api/src/main/java/net/klesatschke/threema/api/results/UploadResult.java similarity index 74% rename from source/src/test/java/ch/threema/apitool/Assert.java rename to api/src/main/java/net/klesatschke/threema/api/results/UploadResult.java index e3dce18..72595c5 100644 --- a/source/src/test/java/ch/threema/apitool/Assert.java +++ b/api/src/main/java/net/klesatschke/threema/api/results/UploadResult.java @@ -21,18 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE */ -package ch.threema.apitool; -import java.util.Arrays; +package net.klesatschke.threema.api.results; -/** - * Assert - */ -public class Assert extends org.junit.Assert { - public static void assertEquals(byte[] expected, byte[] actual) { - assertEquals(null, expected, actual); - } - public static void assertEquals(String message, byte[] expected, byte[] actual) { - assertEquals(message, Arrays.toString(expected), Arrays.toString(actual)); - } +import lombok.Value; + +/** Result of a file upload */ +@Value +public class UploadResult { + private final int responseCode; + private final byte[] blobId; + + /** @return whether the upload succeeded */ + public boolean isSuccess() { + return this.responseCode == 200; + } } diff --git a/api/src/test/java/net/klesatschke/threema/api/APIConnectorTest.java b/api/src/test/java/net/klesatschke/threema/api/APIConnectorTest.java new file mode 100644 index 0000000..a4a2d4b --- /dev/null +++ b/api/src/test/java/net/klesatschke/threema/api/APIConnectorTest.java @@ -0,0 +1,90 @@ +package net.klesatschke.threema.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIOException; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import java.io.IOException; + +import javax.net.ssl.HttpsURLConnection; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockserver.integration.ClientAndServer; +import org.mockserver.junit.jupiter.MockServerExtension; +import org.mockserver.junit.jupiter.MockServerSettings; +import org.mockserver.logging.MockServerLogger; +import org.mockserver.socket.PortFactory; +import org.mockserver.socket.tls.KeyStoreFactory; + +@ExtendWith(MockServerExtension.class) +@ExtendWith(MockitoExtension.class) +@MockServerSettings(ports = {8787, 8888}) +class APIConnectorTest { + private static ClientAndServer client; + + @Mock PublicKeyStore keystore; + + private APIConnector apiConnector; + + @BeforeAll + static void startMockServer() { + // ensure all connection using HTTPS will use the SSL context defined by + // MockServer to allow dynamically generated certificates to be accepted + HttpsURLConnection.setDefaultSSLSocketFactory( + new KeyStoreFactory(new MockServerLogger()).sslContext().getSocketFactory()); + client = ClientAndServer.startClientAndServer(PortFactory.findFreePort()); + } + + @BeforeEach + void setup() { + client.reset(); + apiConnector = + new APIConnector("ID", "secret", "https://localhost:" + client.getPort() + "/", keystore); + } + + @Test + void testLookupEmail() throws IOException { + // GIVEN + var email = "test@mail.box"; + var hash = DataUtils.byteArrayToHexString(CryptTool.hashEmail(email)); + var path = "/lookup/email_hash/" + hash; + var threemaID = "the ID"; + client.when(request().withMethod("GET").withPath(path)).respond(response().withBody(threemaID)); + + assertThat(apiConnector.lookupEmail(email)).isEqualTo(threemaID); + } + + @Test + void testLookupPhone() throws IOException { + // GIVEN + var number = "+49172989127128"; + var hash = DataUtils.byteArrayToHexString(CryptTool.hashPhoneNo(number)); + var path = "/lookup/phone_hash/" + hash; + var threemaID = "the ID"; + client.when(request().withMethod("GET").withPath(path)).respond(response().withBody(threemaID)); + + assertThat(apiConnector.lookupPhone(number)).isEqualTo(threemaID); + } + + @ParameterizedTest + @ValueSource(ints = {400, 401, 404, 500}) + void testLookupPhoneErrors(int httpStatusCode) throws IOException { + // GIVEN + var number = "+49172989127128"; + var hash = DataUtils.byteArrayToHexString(CryptTool.hashPhoneNo(number)); + var path = "/lookup/phone_hash/" + hash; + client + .when(request().withMethod("GET").withPath(path)) + .respond(response().withStatusCode(httpStatusCode)); + + assertThatIOException().isThrownBy(() -> apiConnector.lookupPhone(number)); + } +} diff --git a/api/src/test/java/net/klesatschke/threema/api/Common.java b/api/src/test/java/net/klesatschke/threema/api/Common.java new file mode 100644 index 0000000..bfe4e36 --- /dev/null +++ b/api/src/test/java/net/klesatschke/threema/api/Common.java @@ -0,0 +1,57 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api; + +/** Common Stuff */ +public abstract class Common { + + public static final String myPrivateKey = + "private:94af3260fa2a19adc8e82e82be598be15bc6ad6f47c8ee303cb185ef860e16d2"; + public static final String myPrivateKeyExtract = + "94af3260fa2a19adc8e82e82be598be15bc6ad6f47c8ee303cb185ef860e16d2"; + + public static final String myPublicKey = + "public:3851ad59c96146a05b32e41c0ccd0fd639dc8cd66bf6e1cbd3c8d67e4e8f5531"; + public static final String myPublicKeyExtract = + "3851ad59c96146a05b32e41c0ccd0fd639dc8cd66bf6e1cbd3c8d67e4e8f5531"; + + public static final String otherPrivateKey = + "private:8318e05220acd38e97ba41a9a6318688214219916075ca060f9339a6d1f7fc29"; + public static final String otherPublicKey = + "public:10ac7fd937eafb806f9a05bf9afa340a99387b0063cc9cb0d1ea5505d39cc076"; + + public static final String echochoPublicKey = + "public:4a6a1b34dcef15d43cb74de2fd36091be99fbbaf126d099d47d83d919712c72b"; + public static final String randomNonce = "516f4f1562dda0704a7bae8997cf0b354c6980181152ac32"; + + public static boolean hasContent(byte[] byteArray) { + for (byte b : byteArray) { + if (b != 0) { + return true; + } + } + return false; + } +} diff --git a/api/src/test/java/net/klesatschke/threema/api/CryptToolTest.java b/api/src/test/java/net/klesatschke/threema/api/CryptToolTest.java new file mode 100644 index 0000000..14cf182 --- /dev/null +++ b/api/src/test/java/net/klesatschke/threema/api/CryptToolTest.java @@ -0,0 +1,99 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.neilalexander.jnacl.NaCl; + +import net.klesatschke.threema.api.messages.TextMessage; + +class CryptToolTest { + + @Test + void testRandomNonce() throws Exception { + var randomNonce = CryptTool.randomNonce(); + + // with a length of 24 + assertThat(randomNonce).hasSize(NaCl.NONCEBYTES); + } + + @Test + void testCreateKeyPair() { + var privateKey = new byte[NaCl.SECRETKEYBYTES]; + var publicKey = new byte[NaCl.PUBLICKEYBYTES]; + + CryptTool.generateKeyPair(privateKey, publicKey); + + assertThat(privateKey).isNotNull().satisfies(Common::hasContent); + assertThat(publicKey).isNotNull().satisfies(Common::hasContent); + } + + @Test + void testDecrypt() throws Exception { + var nonce = "0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0"; + var box = + "45181c7aed95a1c100b1b559116c61b43ce15d04014a805288b7d14bf3a993393264fe554794ce7d6007233e8ef5a0f1ccdd704f34e7c7b77c72c239182caf1d061d6fff6ffbbfe8d3b8f3475c2fe352e563aa60290c666b2e627761e32155e62f048b52ef2f39c13ac229f393c67811749467396ecd09f42d32a4eb419117d0451056ac18fac957c52b0cca67568e2d97e5a3fd829a77f914a1ad403c5909fd510a313033422ea5db71eaf43d483238612a54cb1ecfe55259b1de5579e67c6505df7d674d34a737edf721ea69d15b567bc2195ec67e172f3cb8d6842ca88c29138cc33e9351dbc1e4973a82e1cf428c1c763bb8f3eb57770f914a"; + + var privateKey = Key.decodeKey(Common.otherPrivateKey); + var publicKey = Key.decodeKey(Common.myPublicKey); + + var message = + CryptTool.decryptMessage( + DataUtils.hexStringToByteArray(box), + privateKey.getKey(), + publicKey.getKey(), + DataUtils.hexStringToByteArray(nonce)); + + assertThat(message) + .isInstanceOf(TextMessage.class) + .extracting(msg -> ((TextMessage) msg).getText()) + .isEqualTo("Dies ist eine Testnachricht. äöü"); + } + + @Test + void testEncrypt() throws Exception { + var text = "Dies ist eine Testnachricht. äöü"; + + var privateKey = Key.decodeKey(Common.myPrivateKey); + var publicKey = Key.decodeKey(Common.otherPublicKey); + + var res = CryptTool.encryptTextMessage(text, privateKey.getKey(), publicKey.getKey()); + + assertThat(res).isNotNull(); + assertThat(res.getNonce()).isNotNull().satisfies(Common::hasContent); + assertThat(res.getResult()).isNotNull().satisfies(Common::hasContent); + } + + @Test + void testDerivePublicKey() throws Exception { + var privateKey = Key.decodeKey(Common.myPrivateKey); + var publicKey = Key.decodeKey(Common.myPublicKey); + var derivedPublicKey = CryptTool.derivePublicKey(privateKey.getKey()); + assertThat(derivedPublicKey).isEqualTo(publicKey.getKey()); + } +} diff --git a/api/src/test/java/net/klesatschke/threema/api/KeyTest.java b/api/src/test/java/net/klesatschke/threema/api/KeyTest.java new file mode 100644 index 0000000..8510d78 --- /dev/null +++ b/api/src/test/java/net/klesatschke/threema/api/KeyTest.java @@ -0,0 +1,73 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.junit.jupiter.api.Test; + +import net.klesatschke.threema.api.exceptions.InvalidKeyException; + +class KeyTest { + + @Test + void testDecodeWrongKey() { + assertThatExceptionOfType(InvalidKeyException.class) + .isThrownBy(() -> Key.decodeKey("imnotarealkey")); + } + + @Test + void testDecodeKeyPrivate() throws Exception { + var key = + Key.decodeKey("private:1234567890123456789012345678901234567890123456789012345678901234"); + + assertThat(key).isNotNull(); + assertThat(key.getType()).isEqualTo(Key.KeyType.PRIVATE); + assertThat(key.getKey()) + .isEqualTo( + DataUtils.hexStringToByteArray( + "1234567890123456789012345678901234567890123456789012345678901234")); + } + + @Test + void testDecodeKeyPublic() throws Exception { + var key = + Key.decodeKey("public:1234567890123456789012345678901234567890123456789012345678901234"); + assertThat(key).isNotNull(); + assertThat(key.getType()).isEqualTo(Key.KeyType.PUBLIC); + assertThat(key.getKey()) + .isEqualTo( + DataUtils.hexStringToByteArray( + "1234567890123456789012345678901234567890123456789012345678901234")); + } + + @Test + void testEncodePrivate() throws Exception { + var keyAsByte = DataUtils.hexStringToByteArray(Common.myPrivateKeyExtract); + var key = new Key(Key.KeyType.PRIVATE, keyAsByte); + assertThat(key.getType()).isEqualTo(Key.KeyType.PRIVATE); + assertThat(key.getKey()).isEqualTo(keyAsByte); + assertThat(key.encode()).isEqualTo(Common.myPrivateKey); + } +} diff --git a/source/pom.xml b/cli/pom.xml similarity index 53% rename from source/pom.xml rename to cli/pom.xml index 4fafe59..e1b5937 100644 --- a/source/pom.xml +++ b/cli/pom.xml @@ -3,20 +3,14 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - ch.threema.apitool - msgapi-sdk-java - 1.1.4 - Threema MsgApi SDK - - UTF-8 - ${source.encoding} - ${source.encoding} - - - Threema GmbH, Staldenbachstrasse 11, 8808 Pfäffikon SZ, Schweiz - https://www.threema.ch - - https://gateway.threema.ch/ + + net.klesatschke.threema + msgapi-sdk-java + 1.1.5-SNAPSHOT + + cli + Threema Messsage Gateway CLI + https://github.com/lordyavin/threema-msgapi-sdk-java MIT-License @@ -24,52 +18,22 @@ repo - - - junit - junit - 4.13.1 - test - - - commons-io - commons-io - 2.10.0 - - - org.apache.commons - commons-lang3 - 3.12.0 - - - com.google.code.gson - gson - 2.8.9 - - + + net.klesatschke.threema.cli.ConsoleMain + - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${source.encoding} - 11 - 11 - - org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.3.0 true lib/ - ch.threema.apitool.Console + ${mainClass} @@ -81,7 +45,7 @@ - ch.threema.apitool.ConsoleMain + ${mainClass} @@ -100,6 +64,13 @@ + + + net.klesatschke.threema + api + 1.1.5-SNAPSHOT + + github diff --git a/cli/src/main/java/net/klesatschke/threema/cli/ConsoleMain.java b/cli/src/main/java/net/klesatschke/threema/cli/ConsoleMain.java new file mode 100644 index 0000000..3949715 --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/ConsoleMain.java @@ -0,0 +1,217 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ +package net.klesatschke.threema.cli; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; + +import net.klesatschke.threema.api.APIConnector; +import net.klesatschke.threema.api.CryptTool; +import net.klesatschke.threema.cli.console.commands.CapabilityCommand; +import net.klesatschke.threema.cli.console.commands.Command; +import net.klesatschke.threema.cli.console.commands.CreditsCommand; +import net.klesatschke.threema.cli.console.commands.DecryptAndDownloadCommand; +import net.klesatschke.threema.cli.console.commands.DecryptCommand; +import net.klesatschke.threema.cli.console.commands.DerivePublicKeyCommand; +import net.klesatschke.threema.cli.console.commands.EncryptCommand; +import net.klesatschke.threema.cli.console.commands.FetchPublicKey; +import net.klesatschke.threema.cli.console.commands.GenerateKeyPairCommand; +import net.klesatschke.threema.cli.console.commands.HashEmailCommand; +import net.klesatschke.threema.cli.console.commands.HashPhoneCommand; +import net.klesatschke.threema.cli.console.commands.IDLookupByEmail; +import net.klesatschke.threema.cli.console.commands.IDLookupByPhoneNo; +import net.klesatschke.threema.cli.console.commands.SendE2EFileMessageCommand; +import net.klesatschke.threema.cli.console.commands.SendE2EImageMessageCommand; +import net.klesatschke.threema.cli.console.commands.SendE2ETextMessageCommand; +import net.klesatschke.threema.cli.console.commands.SendSimpleMessageCommand; + +/** + * Command line interface for {@link CryptTool} and {@link APIConnector} operations for testing + * purposes and simple invocation from other programming languages. + */ +public class ConsoleMain { + + static class Commands { + protected final List commandGroups = new ArrayList<>(); + + public CommandGroup create(String description) { + var g = new CommandGroup(description); + this.commandGroups.add(g); + return g; + } + + public ArgumentCommand find(String... arguments) { + if (arguments.length > 0) { + for (CommandGroup g : this.commandGroups) { + var c = g.find(arguments); + if (c != null) { + return c; + } + } + } + return null; + } + } + + static class CommandGroup { + protected final String description; + protected List argumentCommands = new ArrayList<>(); + + CommandGroup(String description) { + this.description = description; + } + + public CommandGroup add(Command command, String... arguments) { + this.argumentCommands.add(new ArgumentCommand(arguments, command)); + return this; + } + + public ArgumentCommand find(String... arguments) { + ArgumentCommand matchedArgumentCommand = null; + var argMatchedSize = -1; + for (ArgumentCommand c : this.argumentCommands) { + var matched = true; + var matchedSize = 0; + for (var n = 0; n < c.arguments.length; n++) { + if (n > arguments.length || !c.arguments[n].equals(arguments[n])) { + matched = false; + break; + } + matchedSize++; + } + + if (matched && matchedSize > argMatchedSize) { + matchedArgumentCommand = c; + argMatchedSize = matchedSize; + } + } + return matchedArgumentCommand; + } + } + + static class ArgumentCommand { + protected final String[] arguments; + protected final Command command; + + ArgumentCommand(String[] arguments, Command command) { + this.arguments = arguments; + this.command = command; + } + + public void run(String[] givenArguments) throws Exception { + if (givenArguments.length < this.arguments.length) { + throw new Exception("invalid arguments"); + } + + this.command.run( + ArrayUtils.subarray(givenArguments, this.arguments.length, givenArguments.length)); + } + } + + private static final Commands commands = new Commands(); + + public static void main(String[] args) throws Exception { + + commands + .create("Local operations (no network communication)") + .add(new EncryptCommand(), "-e") + .add(new DecryptCommand(), "-d") + .add(new HashEmailCommand(), "-h", "-e") + .add(new HashPhoneCommand(), "-h", "-p") + .add(new GenerateKeyPairCommand(), "-g") + .add(new DerivePublicKeyCommand(), "-p"); + + commands + .create("Network operations") + .add(new SendSimpleMessageCommand(), "-s") + .add(new SendE2ETextMessageCommand(), "-S") + .add(new SendE2EImageMessageCommand(), "-S", "-i") + .add(new SendE2EFileMessageCommand(), "-S", "-f") + .add(new IDLookupByEmail(), "-l", "-e") + .add(new IDLookupByPhoneNo(), "-l", "-p") + .add(new FetchPublicKey(), "-l", "-k") + .add(new CapabilityCommand(), "-c") + .add(new DecryptAndDownloadCommand(), "-D") + .add(new CreditsCommand(), "-C"); + + var argumentCommand = commands.find(args); + if (argumentCommand == null) { + usage(args.length == 1 && "html".equals(args[0])); + } else { + argumentCommand.run(args); + } + } + + private static void usage(boolean htmlOutput) { + if (!htmlOutput) { + System.out.println("version:" + ConsoleMain.class.getPackage().getImplementationVersion()); + + System.out.println("usage:\n"); + + System.out.println("General information"); + System.out.println("-------------------\n"); + + System.out.println("Where a key needs to be specified, it can either be given directly as"); + System.out.println("a command line parameter (in hex with a prefix indicating the type;"); + System.out.println("not recommended on shared machines as other users may be able to see"); + System.out.println("the arguments), or as the path to a file that it should be read from"); + System.out.println("(file contents also in hex with the prefix).\n"); + } + + var groupDescriptionTemplate = + htmlOutput ? "

%s

\n" : "\n%s\n" + StringUtils.repeat("-", 80) + "\n\n"; + var commandTemplate = + htmlOutput ? "
java -jar threema-msgapi-tool.jar %s
\n" : "%s\n"; + + for (CommandGroup commandGroup : commands.commandGroups) { + System.out.format(groupDescriptionTemplate, commandGroup.description); + + for (ArgumentCommand argumentCommand : commandGroup.argumentCommands) { + var command = new StringBuilder(); + for (String argument : argumentCommand.arguments) { + command.append(argument).append(" "); + } + var argumentDescription = argumentCommand.command.getUsageArguments(); + if (htmlOutput) { + System.out.format("

%s

\n", argumentCommand.command.getSubject()); + argumentDescription = StringEscapeUtils.escapeHtml3(argumentDescription); + } + command.append(argumentDescription); + + System.out.format(commandTemplate, command.toString().trim()); + + var description = argumentCommand.command.getUsageDescription(); + if (htmlOutput) { + System.out.format("

%s

\n\n", description); + } else { + System.out.println( + " " + org.apache.commons.text.WordUtils.wrap(description, 76, "\n ", false)); + System.out.println(""); + } + } + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/CapabilityCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/CapabilityCommand.java similarity index 54% rename from source/src/main/java/ch/threema/apitool/console/commands/CapabilityCommand.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/CapabilityCommand.java index 8037f11..d6c45ad 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/CapabilityCommand.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/CapabilityCommand.java @@ -22,35 +22,34 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; -import ch.threema.apitool.results.CapabilityResult; +import net.klesatschke.threema.api.results.CapabilityResult; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; public class CapabilityCommand extends Command { - private final ThreemaIDField threemaIdField; - private final ThreemaIDField fromField; - private final TextField secretField; - - public CapabilityCommand() { - super("Fetch Capability", - "Fetch the capability of a Threema ID"); - - this.threemaIdField = this.createThreemaId("id"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - } - - @Override - protected void execute() throws Exception { - String threemaId = this.threemaIdField.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); - - CapabilityResult capabilities = this.createConnector(from, secret) - .lookupKeyCapability(threemaId); - - System.out.println(capabilities); - } + private final ThreemaIDField threemaIdField; + private final ThreemaIDField fromField; + private final TextField secretField; + + public CapabilityCommand() { + super("Fetch Capability", "Fetch the capability of a Threema ID"); + + this.threemaIdField = this.createThreemaId("id"); + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + } + + @Override + protected void execute() throws Exception { + String threemaId = this.threemaIdField.getValue(); + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); + + CapabilityResult capabilities = + this.createConnector(from, secret).lookupKeyCapability(threemaId); + + System.out.println(capabilities); + } } diff --git a/cli/src/main/java/net/klesatschke/threema/cli/console/commands/Command.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/Command.java new file mode 100644 index 0000000..b9ae086 --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/Command.java @@ -0,0 +1,214 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.console.commands; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.LinkedList; +import java.util.List; + +import net.klesatschke.threema.api.APIConnector; +import net.klesatschke.threema.api.PublicKeyStore; +import net.klesatschke.threema.cli.console.commands.fields.ByteArrayField; +import net.klesatschke.threema.cli.console.commands.fields.Field; +import net.klesatschke.threema.cli.console.commands.fields.FileField; +import net.klesatschke.threema.cli.console.commands.fields.FolderField; +import net.klesatschke.threema.cli.console.commands.fields.PrivateKeyField; +import net.klesatschke.threema.cli.console.commands.fields.PublicKeyField; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; + +public abstract class Command { + private final List fields = new LinkedList<>(); + private final String subject; + private final String description; + + public Command(String subject, String description) { + this.subject = subject; + this.description = description; + } + + private void addField(Field f) { + if (f.isRequired()) { + int pos = this.fields.size(); + // add after last required + for (int n = 0; n < this.fields.size(); n++) { + if (!this.fields.get(n).isRequired()) { + pos = n; + break; + } + } + this.fields.add(pos, f); + } else { + this.fields.add(f); + } + } + + protected TextField createTextField(String key) { + return this.createTextField(key, true); + } + + protected TextField createTextField(String key, boolean required) { + TextField field = new TextField(key, required); + this.addField(field); + return field; + } + + protected ThreemaIDField createThreemaId(String key) { + return this.createThreemaId(key, true); + } + + protected ThreemaIDField createThreemaId(String key, boolean required) { + ThreemaIDField field = new ThreemaIDField(key, required); + this.addField(field); + return field; + } + + protected PublicKeyField createPublicKeyField(String key) { + return this.createPublicKeyField(key, true); + } + + protected PublicKeyField createPublicKeyField(String key, boolean required) { + PublicKeyField field = new PublicKeyField(key, required); + this.addField(field); + return field; + } + + protected PrivateKeyField createPrivateKeyField(String key) { + return this.createPrivateKeyField(key, true); + } + + protected PrivateKeyField createPrivateKeyField(String key, boolean required) { + PrivateKeyField field = new PrivateKeyField(key, required); + this.addField(field); + return field; + } + + protected FileField createFileField(String key) { + return this.createFileField(key, true); + } + + protected FileField createFileField(String key, boolean required) { + FileField field = new FileField(key, required); + this.addField(field); + return field; + } + + protected FolderField createFolderField(String key) { + return this.createFolderField(key, true); + } + + protected FolderField createFolderField(String key, boolean required) { + FolderField field = new FolderField(key, required); + this.addField(field); + return field; + } + + protected ByteArrayField createByteArrayField(String key) { + return this.createByteArrayField(key, true); + } + + protected ByteArrayField createByteArrayField(String key, boolean required) { + ByteArrayField field = new ByteArrayField(key, required); + this.addField(field); + return field; + } + + protected APIConnector createConnector(String gatewayId, String secret) { + return new APIConnector( + gatewayId, + secret, + new PublicKeyStore() { + @Override + protected byte[] fetchPublicKey(String threemaId) { + return null; + } + + @Override + protected void save(String threemaId, byte[] publicKey) { + // do nothing + } + }); + } + + protected String readStream(InputStream stream, String charset) throws IOException { + try { + Reader reader = new BufferedReader(new InputStreamReader(stream, charset)); + StringBuilder builder = new StringBuilder(); + char[] buffer = new char[8192]; + int read; + while ((read = reader.read(buffer, 0, buffer.length)) > 0) { + builder.append(buffer, 0, read); + } + return builder.toString(); + } finally { + stream.close(); + } + } + + public final void run(String[] arguments) throws Exception { + int pos = 0; + for (Field f : this.fields) { + if (arguments.length > pos) { + f.setValue(arguments[pos]); + } + pos++; + } + + // validate + for (Field f : this.fields) { + if (!f.isValid()) { + return; + } + } + + this.execute(); + } + + public final String getSubject() { + return this.subject; + } + + public final String getUsageArguments() { + StringBuilder usage = new StringBuilder(); + for (Field f : this.fields) { + usage + .append(" ") + .append(f.isRequired() ? "<" : "[") + .append(f.getKey()) + .append(f.isRequired() ? ">" : "]"); + } + return usage.toString().trim(); + } + + public final String getUsageDescription() { + return this.description; + } + + protected abstract void execute() throws Exception; +} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/CreditsCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/CreditsCommand.java similarity index 57% rename from source/src/main/java/ch/threema/apitool/console/commands/CreditsCommand.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/CreditsCommand.java index 1cb09e8..27451c8 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/CreditsCommand.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/CreditsCommand.java @@ -22,34 +22,33 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; public class CreditsCommand extends Command { - private final ThreemaIDField fromField; - private final TextField secretField; + private final ThreemaIDField fromField; + private final TextField secretField; - public CreditsCommand() { - super("Credits", "Fetch the remaining credits"); + public CreditsCommand() { + super("Credits", "Fetch the remaining credits"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - } + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + } - @Override - protected void execute() throws Exception { - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); + @Override + protected void execute() throws Exception { + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); - Integer credits = this.createConnector(from, secret).lookupCredits(); + Integer credits = this.createConnector(from, secret).lookupCredits(); - if (credits != null) { - System.out.println("Remaining credits: " + credits); - } else { - System.out.println("Error fetching credits"); - ; - } + if (credits != null) { + System.out.println("Remaining credits: " + credits); + } else { + System.out.println("Error fetching credits"); } + } } diff --git a/cli/src/main/java/net/klesatschke/threema/cli/console/commands/DecryptAndDownloadCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/DecryptAndDownloadCommand.java new file mode 100644 index 0000000..c071f59 --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/DecryptAndDownloadCommand.java @@ -0,0 +1,78 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.console.commands; + +import java.nio.file.Path; + +import net.klesatschke.threema.api.DataUtils; +import net.klesatschke.threema.cli.console.commands.fields.ByteArrayField; +import net.klesatschke.threema.cli.console.commands.fields.FolderField; +import net.klesatschke.threema.cli.console.commands.fields.PrivateKeyField; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; +import net.klesatschke.threema.cli.helpers.E2EHelper; + +public class DecryptAndDownloadCommand extends Command { + private final ThreemaIDField threemaId; + private final ThreemaIDField fromField; + private final TextField secretField; + private final PrivateKeyField privateKeyField; + private final ByteArrayField nonceField; + private final FolderField outputFolderField; + private final TextField messageIdField; + + public DecryptAndDownloadCommand() { + super( + "Decrypt and download", + "Decrypt a box (box from the stdin) message and download (if the message is a image or file message) the file(s) to the defined directory"); + this.threemaId = this.createThreemaId("id"); + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + this.privateKeyField = this.createPrivateKeyField("privateKey"); + this.messageIdField = this.createTextField("messageId"); + this.nonceField = this.createByteArrayField("nonce"); + this.outputFolderField = this.createFolderField("outputFolder", false); + } + + @Override + protected void execute() throws Exception { + String id = this.threemaId.getValue(); + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); + byte[] privateKey = this.privateKeyField.getValue(); + byte[] nonce = this.nonceField.getValue(); + String messageId = this.messageIdField.getValue(); + Path outputFolder = this.outputFolderField.getValue(); + + E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); + + byte[] box = DataUtils.hexStringToByteArray(this.readStream(System.in, "UTF-8").trim()); + + E2EHelper.ReceiveMessageResult res = + e2EHelper.receiveMessage(id, messageId, box, nonce, outputFolder); + System.out.println(res.toString()); + System.out.println(res.getFiles().toString()); + } +} diff --git a/cli/src/main/java/net/klesatschke/threema/cli/console/commands/DecryptCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/DecryptCommand.java new file mode 100644 index 0000000..bc18ec0 --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/DecryptCommand.java @@ -0,0 +1,62 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.console.commands; + +import net.klesatschke.threema.api.CryptTool; +import net.klesatschke.threema.api.DataUtils; +import net.klesatschke.threema.api.messages.ThreemaMessage; +import net.klesatschke.threema.cli.console.commands.fields.ByteArrayField; +import net.klesatschke.threema.cli.console.commands.fields.PrivateKeyField; +import net.klesatschke.threema.cli.console.commands.fields.PublicKeyField; + +public class DecryptCommand extends Command { + private final PrivateKeyField privateKeyField; + private final PublicKeyField publicKeyField; + private final ByteArrayField nonceField; + + public DecryptCommand() { + super( + "Decrypt", + "Decrypt standard input using the given recipient private key and sender public key. The nonce must be given on the command line, and the box (hex) on standard input. Prints the decrypted message to standard output."); + + this.privateKeyField = this.createPrivateKeyField("privateKey"); + this.publicKeyField = this.createPublicKeyField("publicKey"); + this.nonceField = this.createByteArrayField("nonce"); + } + + @Override + protected void execute() throws Exception { + byte[] privateKey = this.privateKeyField.getValue(); + byte[] publicKey = this.publicKeyField.getValue(); + byte[] nonce = this.nonceField.getValue(); + + /* read box from stdin */ + byte[] box = DataUtils.hexStringToByteArray(readStream(System.in, "UTF-8")); + + ThreemaMessage message = CryptTool.decryptMessage(box, privateKey, publicKey, nonce); + + System.out.println(message); + } +} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/DerivePublicKeyCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/DerivePublicKeyCommand.java similarity index 60% rename from source/src/main/java/ch/threema/apitool/console/commands/DerivePublicKeyCommand.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/DerivePublicKeyCommand.java index 32c9e1e..f9913f8 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/DerivePublicKeyCommand.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/DerivePublicKeyCommand.java @@ -22,28 +22,29 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; -import ch.threema.apitool.console.commands.fields.PrivateKeyField; -import ch.threema.apitool.CryptTool; -import ch.threema.apitool.Key; +import net.klesatschke.threema.api.CryptTool; +import net.klesatschke.threema.api.Key; +import net.klesatschke.threema.api.Key.KeyType; +import net.klesatschke.threema.cli.console.commands.fields.PrivateKeyField; public class DerivePublicKeyCommand extends Command { - private final PrivateKeyField privateKeyField; + private final PrivateKeyField privateKeyField; - public DerivePublicKeyCommand() { - super("Derive Public Key", - "Derive the public key that corresponds with the given private key."); + public DerivePublicKeyCommand() { + super( + "Derive Public Key", "Derive the public key that corresponds with the given private key."); - this.privateKeyField = this.createPrivateKeyField("privateKey"); - } + this.privateKeyField = this.createPrivateKeyField("privateKey"); + } - @Override - protected void execute() throws Exception { - byte[] privateKey = this.privateKeyField.getValue(); - byte[] publicKey = CryptTool.derivePublicKey(privateKey); + @Override + protected void execute() throws Exception { + var privateKey = this.privateKeyField.getValue(); + var publicKey = CryptTool.derivePublicKey(privateKey); - System.out.println("res"); - System.out.println(new Key(Key.KeyType.PUBLIC, publicKey).encode()); - } + System.out.println("res"); + System.out.println(new Key(KeyType.PUBLIC, publicKey).encode()); + } } diff --git a/cli/src/main/java/net/klesatschke/threema/cli/console/commands/EncryptCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/EncryptCommand.java new file mode 100644 index 0000000..abf798b --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/EncryptCommand.java @@ -0,0 +1,59 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.console.commands; + +import net.klesatschke.threema.api.CryptTool; +import net.klesatschke.threema.api.DataUtils; +import net.klesatschke.threema.api.results.EncryptResult; +import net.klesatschke.threema.cli.console.commands.fields.PrivateKeyField; +import net.klesatschke.threema.cli.console.commands.fields.PublicKeyField; + +public class EncryptCommand extends Command { + private final PrivateKeyField privateKeyField; + private final PublicKeyField publicKeyField; + + public EncryptCommand() { + super( + "Encrypt", + "Encrypt standard input using the given sender private key and recipient public key. Prints two lines to standard output: first the nonce (hex), and then the box (hex)."); + + this.privateKeyField = this.createPrivateKeyField("privateKey"); + this.publicKeyField = this.createPublicKeyField("publicKey"); + } + + @Override + protected void execute() throws Exception { + byte[] privateKey = this.privateKeyField.getValue(); + byte[] publicKey = this.publicKeyField.getValue(); + + /* read text from stdin */ + String text = readStream(System.in, "UTF-8").trim(); + + EncryptResult res = CryptTool.encryptTextMessage(text, privateKey, publicKey); + + System.out.println(DataUtils.byteArrayToHexString(res.getNonce())); + System.out.println(DataUtils.byteArrayToHexString(res.getResult())); + } +} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/FetchPublicKey.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/FetchPublicKey.java similarity index 52% rename from source/src/main/java/ch/threema/apitool/console/commands/FetchPublicKey.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/FetchPublicKey.java index d586fd9..4a8432b 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/FetchPublicKey.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/FetchPublicKey.java @@ -22,37 +22,36 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; -import ch.threema.apitool.APIConnector; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; -import ch.threema.apitool.Key; +import net.klesatschke.threema.api.Key; +import net.klesatschke.threema.api.Key.KeyType; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; public class FetchPublicKey extends Command { - private final ThreemaIDField threemaIdField; - private final ThreemaIDField fromField; - private final TextField secretField; + private final ThreemaIDField threemaIdField; + private final ThreemaIDField fromField; + private final TextField secretField; - public FetchPublicKey() { - super("Fetch Public Key", - "Lookup the public key for the given ID."); + public FetchPublicKey() { + super("Fetch Public Key", "Lookup the public key for the given ID."); - this.threemaIdField = this.createThreemaId("id"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - } + this.threemaIdField = this.createThreemaId("id"); + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + } - @Override - protected void execute() throws Exception { - String threemaId = this.threemaIdField.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); + @Override + protected void execute() throws Exception { + var threemaId = this.threemaIdField.getValue(); + var from = this.fromField.getValue(); + var secret = this.secretField.getValue(); - APIConnector apiConnector = this.createConnector(from, secret); - byte[] publicKey = apiConnector.lookupKey(threemaId); - if (publicKey != null) { - System.out.println(new Key(Key.KeyType.PUBLIC, publicKey).encode()); - } - } + var apiConnector = this.createConnector(from, secret); + var publicKey = apiConnector.lookupKey(threemaId); + if (publicKey != null) { + System.out.println(new Key(KeyType.PUBLIC, publicKey).encode()); + } + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/GenerateKeyPairCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/GenerateKeyPairCommand.java similarity index 51% rename from source/src/main/java/ch/threema/apitool/console/commands/GenerateKeyPairCommand.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/GenerateKeyPairCommand.java index d3f176b..32a2fe3 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/GenerateKeyPairCommand.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/GenerateKeyPairCommand.java @@ -22,36 +22,41 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; + +import java.io.File; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; -import ch.threema.apitool.Key; import com.neilalexander.jnacl.NaCl; -import java.io.File; +import net.klesatschke.threema.api.CryptTool; +import net.klesatschke.threema.api.DataUtils; +import net.klesatschke.threema.api.Key; +import net.klesatschke.threema.api.Key.KeyType; +import net.klesatschke.threema.cli.console.commands.fields.TextField; public class GenerateKeyPairCommand extends Command { - private final TextField privateKeyPath; - private final TextField publicKeyPath; - - public GenerateKeyPairCommand() { - super("Generate Key Pair", - "Generate a new key pair and write the private and public keys to the respective files (in hex)."); - this.privateKeyPath = this.createTextField("privateKeyFile"); - this.publicKeyPath = this.createTextField("publicKeyPath"); - } - - @Override - protected void execute() throws Exception { - byte[] privateKey = new byte[NaCl.SECRETKEYBYTES]; - byte[] publicKey = new byte[NaCl.PUBLICKEYBYTES]; - - CryptTool.generateKeyPair(privateKey, publicKey); - - // Write both keys to file - DataUtils.writeKeyFile(new File(this.privateKeyPath.getValue()), new Key(Key.KeyType.PRIVATE, privateKey)); - DataUtils.writeKeyFile(new File(this.publicKeyPath.getValue()), new Key(Key.KeyType.PUBLIC, publicKey)); - } + private final TextField privateKeyPath; + private final TextField publicKeyPath; + + public GenerateKeyPairCommand() { + super( + "Generate Key Pair", + "Generate a new key pair and write the private and public keys to the respective files (in hex)."); + this.privateKeyPath = this.createTextField("privateKeyFile"); + this.publicKeyPath = this.createTextField("publicKeyPath"); + } + + @Override + protected void execute() throws Exception { + var privateKey = new byte[NaCl.SECRETKEYBYTES]; + var publicKey = new byte[NaCl.PUBLICKEYBYTES]; + + CryptTool.generateKeyPair(privateKey, publicKey); + + // Write both keys to file + DataUtils.writeKeyFile( + new File(this.privateKeyPath.getValue()), new Key(KeyType.PRIVATE, privateKey)); + DataUtils.writeKeyFile( + new File(this.publicKeyPath.getValue()), new Key(KeyType.PUBLIC, publicKey)); + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/HashEmailCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/HashEmailCommand.java similarity index 64% rename from source/src/main/java/ch/threema/apitool/console/commands/HashEmailCommand.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/HashEmailCommand.java index 4aa7311..a05909d 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/HashEmailCommand.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/HashEmailCommand.java @@ -22,25 +22,25 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; +import net.klesatschke.threema.api.CryptTool; +import net.klesatschke.threema.api.DataUtils; +import net.klesatschke.threema.cli.console.commands.fields.TextField; public class HashEmailCommand extends Command { - private final TextField emailField; + private final TextField emailField; - public HashEmailCommand() { - super("Hash Email Address", - "Hash an email address for identity lookup. Prints the hash in hex."); + public HashEmailCommand() { + super( + "Hash Email Address", "Hash an email address for identity lookup. Prints the hash in hex."); - this.emailField = this.createTextField("email", true); - } + this.emailField = this.createTextField("email", true); + } - @Override - protected void execute() throws Exception { - byte[] emailHash = CryptTool.hashEmail(this.emailField.getValue()); - System.out.println(DataUtils.byteArrayToHexString(emailHash)); - } + @Override + protected void execute() throws Exception { + byte[] emailHash = CryptTool.hashEmail(this.emailField.getValue()); + System.out.println(DataUtils.byteArrayToHexString(emailHash)); + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/HashPhoneCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/HashPhoneCommand.java similarity index 64% rename from source/src/main/java/ch/threema/apitool/console/commands/HashPhoneCommand.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/HashPhoneCommand.java index 1e349f7..4e7b6ee 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/HashPhoneCommand.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/HashPhoneCommand.java @@ -22,27 +22,26 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; +import net.klesatschke.threema.api.CryptTool; +import net.klesatschke.threema.api.DataUtils; +import net.klesatschke.threema.cli.console.commands.fields.TextField; public class HashPhoneCommand extends Command { - private final TextField phoneNo; + private final TextField phoneNo; - public HashPhoneCommand() { - super("Hash Phone Number", - "Hash a phone number for identity lookup. Prints the hash in hex."); + public HashPhoneCommand() { + super("Hash Phone Number", "Hash a phone number for identity lookup. Prints the hash in hex."); - this.phoneNo = this.createTextField("phoneNo", true); - } + this.phoneNo = this.createTextField("phoneNo", true); + } - @Override - protected void execute() throws Exception { - String phoneNo = this.phoneNo.getValue(); + @Override + protected void execute() throws Exception { + String phoneNo = this.phoneNo.getValue(); - byte[] phoneHash = CryptTool.hashPhoneNo(phoneNo); - System.out.println(DataUtils.byteArrayToHexString(phoneHash)); - } + byte[] phoneHash = CryptTool.hashPhoneNo(phoneNo); + System.out.println(DataUtils.byteArrayToHexString(phoneHash)); + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByEmail.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/IDLookupByEmail.java similarity index 52% rename from source/src/main/java/ch/threema/apitool/console/commands/IDLookupByEmail.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/IDLookupByEmail.java index 39a5ee1..bef664c 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByEmail.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/IDLookupByEmail.java @@ -22,36 +22,37 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; -import ch.threema.apitool.APIConnector; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; +import net.klesatschke.threema.api.APIConnector; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; public class IDLookupByEmail extends Command { - private final TextField emailField; - private final ThreemaIDField fromField; - private final TextField secretField; + private final TextField emailField; + private final ThreemaIDField fromField; + private final TextField secretField; - public IDLookupByEmail() { - super("ID Lookup By Email Address", - "Lookup the ID linked to the given email address (will be hashed locally)."); + public IDLookupByEmail() { + super( + "ID Lookup By Email Address", + "Lookup the ID linked to the given email address (will be hashed locally)."); - this.emailField = this.createTextField("email"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - } + this.emailField = this.createTextField("email"); + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + } - @Override - protected void execute() throws Exception { - String email = this.emailField.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); + @Override + protected void execute() throws Exception { + String email = this.emailField.getValue(); + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); - APIConnector apiConnector = this.createConnector(from, secret); - String id = apiConnector.lookupEmail(email); - if (id != null) { - System.out.println(id); - } - } + APIConnector apiConnector = this.createConnector(from, secret); + String id = apiConnector.lookupEmail(email); + if (id != null) { + System.out.println(id); + } + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByPhoneNo.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/IDLookupByPhoneNo.java similarity index 52% rename from source/src/main/java/ch/threema/apitool/console/commands/IDLookupByPhoneNo.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/IDLookupByPhoneNo.java index e02fb7e..007a8d8 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByPhoneNo.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/IDLookupByPhoneNo.java @@ -22,36 +22,37 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands; +package net.klesatschke.threema.cli.console.commands; -import ch.threema.apitool.APIConnector; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; +import net.klesatschke.threema.api.APIConnector; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; public class IDLookupByPhoneNo extends Command { - private final TextField phoneNoField; - private final ThreemaIDField fromField; - private final TextField secretField; + private final TextField phoneNoField; + private final ThreemaIDField fromField; + private final TextField secretField; - public IDLookupByPhoneNo() { - super("ID Lookup By Phone Number", - "Lookup the ID linked to the given phone number (will be hashed locally)."); + public IDLookupByPhoneNo() { + super( + "ID Lookup By Phone Number", + "Lookup the ID linked to the given phone number (will be hashed locally)."); - this.phoneNoField = this.createTextField("phoneNo"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - } + this.phoneNoField = this.createTextField("phoneNo"); + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + } - @Override - protected void execute() throws Exception { - String phoneNo = this.phoneNoField.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); + @Override + protected void execute() throws Exception { + String phoneNo = this.phoneNoField.getValue(); + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); - APIConnector apiConnector = this.createConnector(from, secret); - String id = apiConnector.lookupPhone(phoneNo); - if (id != null) { - System.out.println(id); - } - } + APIConnector apiConnector = this.createConnector(from, secret); + String id = apiConnector.lookupPhone(phoneNo); + if (id != null) { + System.out.println(id); + } + } } diff --git a/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2EFileMessageCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2EFileMessageCommand.java new file mode 100644 index 0000000..a59edad --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2EFileMessageCommand.java @@ -0,0 +1,68 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.console.commands; + +import java.io.File; + +import net.klesatschke.threema.cli.console.commands.fields.FileField; +import net.klesatschke.threema.cli.console.commands.fields.PrivateKeyField; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; +import net.klesatschke.threema.cli.helpers.E2EHelper; + +public class SendE2EFileMessageCommand extends Command { + private final ThreemaIDField threemaId; + private final ThreemaIDField fromField; + private final TextField secretField; + private final PrivateKeyField privateKeyField; + private final FileField fileField; + private final FileField thumbnailField; + + public SendE2EFileMessageCommand() { + super( + "Send End-to-End Encrypted File Message", + "Encrypt the file (and thumbnail) and send a file message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success."); + this.threemaId = this.createThreemaId("to"); + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + this.privateKeyField = this.createPrivateKeyField("privateKey"); + this.fileField = this.createFileField("file"); + this.thumbnailField = this.createFileField("thumbnail", false); + } + + @Override + protected void execute() throws Exception { + String to = this.threemaId.getValue(); + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); + byte[] privateKey = this.privateKeyField.getValue(); + File file = this.fileField.getValue(); + File thumbnail = this.thumbnailField.getValue(); + + E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); + String messageId = e2EHelper.sendFileMessage(to, file, thumbnail); + System.out.println("MessageId: " + messageId); + } +} diff --git a/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2EImageMessageCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2EImageMessageCommand.java new file mode 100644 index 0000000..4f3556b --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2EImageMessageCommand.java @@ -0,0 +1,66 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.console.commands; + +import java.nio.file.Path; + +import net.klesatschke.threema.cli.console.commands.fields.FolderField; +import net.klesatschke.threema.cli.console.commands.fields.PrivateKeyField; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; +import net.klesatschke.threema.cli.helpers.E2EHelper; + +public class SendE2EImageMessageCommand extends Command { + private final ThreemaIDField toField; + private final ThreemaIDField fromField; + private final TextField secretField; + private final PrivateKeyField privateKeyField; + private final FolderField imageFilePath; + + public SendE2EImageMessageCommand() { + super( + "Send End-to-End Encrypted Image Message", + "Encrypt standard input and send the message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success."); + + this.toField = this.createThreemaId("to", true); + this.fromField = this.createThreemaId("from", true); + this.secretField = this.createTextField("secret", true); + this.privateKeyField = this.createPrivateKeyField("privateKey", true); + this.imageFilePath = this.createFolderField("imageFilePath", true); + } + + @Override + protected void execute() throws Exception { + String to = this.toField.getValue(); + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); + byte[] privateKey = this.privateKeyField.getValue(); + Path imageFilePath = this.imageFilePath.getValue(); + + E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); + String messageId = e2EHelper.sendImageMessage(to, imageFilePath.toString()); + System.out.println("MessageId: " + messageId); + } +} diff --git a/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2ETextMessageCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2ETextMessageCommand.java new file mode 100644 index 0000000..70c0920 --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendE2ETextMessageCommand.java @@ -0,0 +1,61 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.console.commands; + +import net.klesatschke.threema.cli.console.commands.fields.PrivateKeyField; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; +import net.klesatschke.threema.cli.helpers.E2EHelper; + +public class SendE2ETextMessageCommand extends Command { + private final ThreemaIDField threemaId; + private final ThreemaIDField fromField; + private final TextField secretField; + private final PrivateKeyField privateKeyField; + + public SendE2ETextMessageCommand() { + super( + "Send End-to-End Encrypted Text Message", + "Encrypt standard input and send the message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success."); + this.threemaId = this.createThreemaId("to"); + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + this.privateKeyField = this.createPrivateKeyField("privateKey"); + } + + @Override + protected void execute() throws Exception { + String to = this.threemaId.getValue(); + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); + byte[] privateKey = this.privateKeyField.getValue(); + + String text = this.readStream(System.in, "UTF-8").trim(); + + E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); + String messageId = e2EHelper.sendTextMessage(to, text); + System.out.println("MessageId: " + messageId); + } +} diff --git a/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendSimpleMessageCommand.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendSimpleMessageCommand.java new file mode 100644 index 0000000..2ca3472 --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/SendSimpleMessageCommand.java @@ -0,0 +1,58 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.console.commands; + +import net.klesatschke.threema.api.APIConnector; +import net.klesatschke.threema.cli.console.commands.fields.TextField; +import net.klesatschke.threema.cli.console.commands.fields.ThreemaIDField; + +public class SendSimpleMessageCommand extends Command { + private final ThreemaIDField threemaId; + private final ThreemaIDField fromField; + private final TextField secretField; + + public SendSimpleMessageCommand() { + super( + "Send Simple Message", + "Send a message from standard input with server-side encryption to the given ID. 'from' is the API identity and 'secret' is the API secret. Returns the message ID on success."); + + this.threemaId = this.createThreemaId("to"); + this.fromField = this.createThreemaId("from"); + this.secretField = this.createTextField("secret"); + } + + @Override + protected void execute() throws Exception { + String to = this.threemaId.getValue(); + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); + + String text = readStream(System.in, "UTF-8").trim(); + + APIConnector apiConnector = this.createConnector(from, secret); + String messageId = apiConnector.sendTextMessageSimple(to, text); + System.out.println(messageId); + } +} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/ByteArrayField.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/ByteArrayField.java similarity index 81% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/ByteArrayField.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/ByteArrayField.java index f200c13..42f9245 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/ByteArrayField.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/ByteArrayField.java @@ -22,16 +22,16 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; +package net.klesatschke.threema.cli.console.commands.fields; -import ch.threema.apitool.DataUtils; +import net.klesatschke.threema.api.DataUtils; public class ByteArrayField extends Field { - public ByteArrayField(String key, boolean required) { - super(key, required); - } + public ByteArrayField(String key, boolean required) { + super(key, required); + } - public byte[] getValue() { - return DataUtils.hexStringToByteArray(this.value); - } + public byte[] getValue() { + return DataUtils.hexStringToByteArray(this.value); + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/Field.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/Field.java similarity index 52% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/Field.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/Field.java index 7655c5c..28f7721 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/Field.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/Field.java @@ -22,48 +22,48 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; +package net.klesatschke.threema.cli.console.commands.fields; -import ch.threema.apitool.exceptions.InvalidCommandFieldValueException; -import ch.threema.apitool.exceptions.RequiredCommandFieldMissingException; +import net.klesatschke.threema.cli.exceptions.InvalidCommandFieldValueException; +import net.klesatschke.threema.cli.exceptions.RequiredCommandFieldMissingException; public abstract class Field { - private final String key; - private final boolean required; - protected String value; + private final String key; + private final boolean required; + protected String value; - protected Field(String key, boolean required) { - this.key = key; - this.required = required; - } + protected Field(String key, boolean required) { + this.key = key; + this.required = required; + } - public void setValue(String value) { - this.value = value; - } + public void setValue(String value) { + this.value = value; + } - public boolean isRequired() { - return this.required; - } + public boolean isRequired() { + return this.required; + } - public String getKey() { - return this.key; - } + public String getKey() { + return this.key; + } - public boolean isValid() throws RequiredCommandFieldMissingException, InvalidCommandFieldValueException { - if(this.isRequired() && this.value == null) { - throw new RequiredCommandFieldMissingException("required field " + this.key + " not set"); - } + public boolean isValid() + throws RequiredCommandFieldMissingException, InvalidCommandFieldValueException { + if (this.isRequired() && this.value == null) { + throw new RequiredCommandFieldMissingException("required field " + this.key + " not set"); + } - if(!this.validate()) { - throw new InvalidCommandFieldValueException("field " + this.key + " value invalid"); - } + if (!this.validate()) { + throw new InvalidCommandFieldValueException("field " + this.key + " value invalid"); + } - return true; - } - - protected boolean validate() { - return true; - } + return true; + } + protected boolean validate() { + return true; + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/FileField.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/FileField.java similarity index 75% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/FileField.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/FileField.java index 8b9b649..0d02c8a 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/FileField.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/FileField.java @@ -22,28 +22,26 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; +package net.klesatschke.threema.cli.console.commands.fields; import java.io.File; public class FileField extends Field { - public FileField(String key, boolean required) { - super(key, required); - } + public FileField(String key, boolean required) { + super(key, required); + } - public File getValue() { - if(this.value != null) { - return new File(this.value); - } + public File getValue() { + if (this.value != null) { + return new File(this.value); + } - return null; - } + return null; + } - @Override - protected boolean validate() { - return !this.isRequired() - || (this.value != null - && new File(this.value).isFile()); - } + @Override + protected boolean validate() { + return !this.isRequired() || (this.value != null && new File(this.value).isFile()); + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/FolderField.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/FolderField.java similarity index 85% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/FolderField.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/FolderField.java index 194801d..a3f8363 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/FolderField.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/FolderField.java @@ -22,17 +22,17 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; +package net.klesatschke.threema.cli.console.commands.fields; import java.nio.file.Path; import java.nio.file.Paths; public class FolderField extends Field { - public FolderField(String key, boolean required) { - super(key, required); - } + public FolderField(String key, boolean required) { + super(key, required); + } - public Path getValue() { - return Paths.get(this.value); - } + public Path getValue() { + return Paths.get(this.value); + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/KeyField.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/KeyField.java similarity index 63% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/KeyField.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/KeyField.java index b816dcd..d15df28 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/KeyField.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/KeyField.java @@ -22,32 +22,32 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; - -import ch.threema.apitool.DataUtils; -import ch.threema.apitool.Key; -import ch.threema.apitool.exceptions.InvalidKeyException; +package net.klesatschke.threema.cli.console.commands.fields; import java.io.File; import java.io.IOException; -public abstract class KeyField extends Field { - public KeyField(String key, boolean required) { - super(key, required); - } - +import net.klesatschke.threema.api.DataUtils; +import net.klesatschke.threema.api.Key; +import net.klesatschke.threema.api.Key.KeyType; +import net.klesatschke.threema.api.exceptions.InvalidKeyException; - byte[] readKey(String argument, String expectedKeyType) throws IOException, InvalidKeyException { - Key key; - - // Try to open a file with that name - File keyFile = new File(argument); - if (keyFile.isFile()) { - key = DataUtils.readKeyFile(keyFile, expectedKeyType); - } else { - key = Key.decodeKey(argument, expectedKeyType); - } - - return key.key; - } +public abstract class KeyField extends Field { + protected KeyField(String key, boolean required) { + super(key, required); + } + + byte[] readKey(String argument, KeyType expectedKeyType) throws IOException, InvalidKeyException { + Key key; + + // Try to open a file with that name + var keyFile = new File(argument); + if (keyFile.isFile()) { + key = DataUtils.readKeyFile(keyFile, expectedKeyType); + } else { + key = Key.decodeKey(argument, expectedKeyType); + } + + return key.getKey(); + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/PrivateKeyField.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/PrivateKeyField.java similarity index 71% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/PrivateKeyField.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/PrivateKeyField.java index 9e87e6b..c0d74a2 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/PrivateKeyField.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/PrivateKeyField.java @@ -22,21 +22,21 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; +package net.klesatschke.threema.cli.console.commands.fields; -import ch.threema.apitool.Key; -import ch.threema.apitool.exceptions.InvalidKeyException; +import net.klesatschke.threema.api.Key.KeyType; +import net.klesatschke.threema.api.exceptions.InvalidKeyException; public class PrivateKeyField extends KeyField { - public PrivateKeyField(String key, boolean required) { - super(key, required); - } + public PrivateKeyField(String key, boolean required) { + super(key, required); + } - public byte[] getValue() throws InvalidKeyException { - try { - return this.readKey(this.value, Key.KeyType.PRIVATE); - } catch (Exception e) { - throw new InvalidKeyException("invalid private key"); - } - } + public byte[] getValue() throws InvalidKeyException { + try { + return this.readKey(this.value, KeyType.PRIVATE); + } catch (Exception e) { + throw new InvalidKeyException("invalid private key"); + } + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/PublicKeyField.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/PublicKeyField.java similarity index 71% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/PublicKeyField.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/PublicKeyField.java index 42b8c5a..a51cafe 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/PublicKeyField.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/PublicKeyField.java @@ -22,20 +22,21 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; +package net.klesatschke.threema.cli.console.commands.fields; -import ch.threema.apitool.Key; -import ch.threema.apitool.exceptions.InvalidKeyException; +import net.klesatschke.threema.api.Key.KeyType; +import net.klesatschke.threema.api.exceptions.InvalidKeyException; public class PublicKeyField extends KeyField { - public PublicKeyField(String key, boolean required) { - super(key, required); - } - public byte[] getValue() throws InvalidKeyException { - try { - return this.readKey(this.value, Key.KeyType.PUBLIC); - } catch (Exception e) { - throw new InvalidKeyException("invalid public key"); - } - } + public PublicKeyField(String key, boolean required) { + super(key, required); + } + + public byte[] getValue() throws InvalidKeyException { + try { + return this.readKey(this.value, KeyType.PUBLIC); + } catch (Exception e) { + throw new InvalidKeyException("invalid public key"); + } + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/TextField.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/TextField.java similarity index 85% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/TextField.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/TextField.java index 271e0fc..c2897c5 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/TextField.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/TextField.java @@ -22,14 +22,14 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; +package net.klesatschke.threema.cli.console.commands.fields; public class TextField extends Field { - public TextField(String key, boolean required) { - super(key, required); - } + public TextField(String key, boolean required) { + super(key, required); + } - public String getValue() { - return this.value; - } + public String getValue() { + return this.value; + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/ThreemaIDField.java b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/ThreemaIDField.java similarity index 85% rename from source/src/main/java/ch/threema/apitool/console/commands/fields/ThreemaIDField.java rename to cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/ThreemaIDField.java index a874cc0..ba3ed00 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/ThreemaIDField.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/console/commands/fields/ThreemaIDField.java @@ -22,14 +22,14 @@ * THE SOFTWARE */ -package ch.threema.apitool.console.commands.fields; +package net.klesatschke.threema.cli.console.commands.fields; public class ThreemaIDField extends Field { - public ThreemaIDField(String key, boolean required) { - super(key, required); - } + public ThreemaIDField(String key, boolean required) { + super(key, required); + } - public String getValue() { - return this.value; - } + public String getValue() { + return this.value; + } } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/InvalidCommandFieldValueException.java b/cli/src/main/java/net/klesatschke/threema/cli/exceptions/InvalidCommandFieldValueException.java similarity index 82% rename from source/src/main/java/ch/threema/apitool/exceptions/InvalidCommandFieldValueException.java rename to cli/src/main/java/net/klesatschke/threema/cli/exceptions/InvalidCommandFieldValueException.java index 3aa47be..c782ea1 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/InvalidCommandFieldValueException.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/exceptions/InvalidCommandFieldValueException.java @@ -22,15 +22,13 @@ * THE SOFTWARE */ -package ch.threema.apitool.exceptions; +package net.klesatschke.threema.cli.exceptions; -/** - * Exception that gets thrown on a illegal call. - */ +/** Exception that gets thrown on a illegal call. */ public class InvalidCommandFieldValueException extends Exception { - private static final long serialVersionUID = -6293436769165519745L; + private static final long serialVersionUID = -6293436769165519745L; - public InvalidCommandFieldValueException(String message) { - super(message); - } + public InvalidCommandFieldValueException(String message) { + super(message); + } } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/NotAllowedException.java b/cli/src/main/java/net/klesatschke/threema/cli/exceptions/NotAllowedException.java similarity index 87% rename from source/src/main/java/ch/threema/apitool/exceptions/NotAllowedException.java rename to cli/src/main/java/net/klesatschke/threema/cli/exceptions/NotAllowedException.java index 9927a0f..fae126c 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/NotAllowedException.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/exceptions/NotAllowedException.java @@ -22,12 +22,10 @@ * THE SOFTWARE */ -package ch.threema.apitool.exceptions; +package net.klesatschke.threema.cli.exceptions; -/** - * Exception that gets thrown on a illegal call. - */ +/** Exception that gets thrown on a illegal call. */ public class NotAllowedException extends Exception { - private static final long serialVersionUID = 3032360799153840206L; + private static final long serialVersionUID = 3032360799153840206L; } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/RequiredCommandFieldMissingException.java b/cli/src/main/java/net/klesatschke/threema/cli/exceptions/RequiredCommandFieldMissingException.java similarity index 82% rename from source/src/main/java/ch/threema/apitool/exceptions/RequiredCommandFieldMissingException.java rename to cli/src/main/java/net/klesatschke/threema/cli/exceptions/RequiredCommandFieldMissingException.java index 559eabe..094e634 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/RequiredCommandFieldMissingException.java +++ b/cli/src/main/java/net/klesatschke/threema/cli/exceptions/RequiredCommandFieldMissingException.java @@ -22,15 +22,13 @@ * THE SOFTWARE */ -package ch.threema.apitool.exceptions; +package net.klesatschke.threema.cli.exceptions; -/** - * Exception that gets thrown on a illegal call. - */ +/** Exception that gets thrown on a illegal call. */ public class RequiredCommandFieldMissingException extends Exception { - private static final long serialVersionUID = 2273462399743084938L; + private static final long serialVersionUID = 2273462399743084938L; - public RequiredCommandFieldMissingException(String message) { - super(message); - } + public RequiredCommandFieldMissingException(String message) { + super(message); + } } diff --git a/cli/src/main/java/net/klesatschke/threema/cli/helpers/E2EHelper.java b/cli/src/main/java/net/klesatschke/threema/cli/helpers/E2EHelper.java new file mode 100644 index 0000000..00d8ed4 --- /dev/null +++ b/cli/src/main/java/net/klesatschke/threema/cli/helpers/E2EHelper.java @@ -0,0 +1,324 @@ +/* + * $Id$ + * + * The MIT License (MIT) + * Copyright (c) 2015 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + */ + +package net.klesatschke.threema.cli.helpers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.IOUtils; + +import com.neilalexander.jnacl.NaCl; + +import net.klesatschke.threema.api.APIConnector; +import net.klesatschke.threema.api.CryptTool; +import net.klesatschke.threema.api.exceptions.InvalidKeyException; +import net.klesatschke.threema.api.exceptions.MessageParseException; +import net.klesatschke.threema.api.messages.FileMessage; +import net.klesatschke.threema.api.messages.ImageMessage; +import net.klesatschke.threema.api.messages.ThreemaMessage; +import net.klesatschke.threema.api.results.UploadResult; +import net.klesatschke.threema.cli.exceptions.NotAllowedException; + +/** Helper to handle Threema end-to-end encryption. */ +public class E2EHelper { + private final APIConnector apiConnector; + private final byte[] privateKey; + + public class ReceiveMessageResult { + private final String messageId; + private final ThreemaMessage message; + protected List files = new ArrayList<>(); + protected List errors = new ArrayList<>(); + + public ReceiveMessageResult(String messageId, ThreemaMessage message) { + this.messageId = messageId; + this.message = message; + } + + public List getFiles() { + return this.files; + } + + public List getErrors() { + return this.errors; + } + + public String getMessageId() { + return messageId; + } + + public ThreemaMessage getMessage() { + return message; + } + } + + public E2EHelper(APIConnector apiConnector, byte[] privateKey) { + this.apiConnector = apiConnector; + this.privateKey = privateKey; + } + + /** + * Encrypt a text message and send it to the given recipient. + * + * @param threemaId target Threema ID + * @param text the text to send + * @return generated message ID + */ + public String sendTextMessage(String threemaId, String text) throws Exception { + // fetch public key + var publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new Exception("invalid threema id"); + } + var res = CryptTool.encryptTextMessage(text, this.privateKey, publicKey); + + return this.apiConnector.sendE2EMessage(threemaId, res.getNonce(), res.getResult()); + } + + /** + * Encrypt an image message and send it to the given recipient. + * + * @param threemaId target Threema ID + * @param imageFilePath path to read image data from + * @return generated message ID + * @throws NotAllowedException + * @throws IOException + * @throws InvalidKeyException + */ + public String sendImageMessage(String threemaId, String imageFilePath) + throws NotAllowedException, IOException, InvalidKeyException { + // fetch public key + var publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + + // check capability of a key + var capabilityResult = this.apiConnector.lookupKeyCapability(threemaId); + if (capabilityResult == null || !capabilityResult.canImage()) { + throw new NotAllowedException(); + } + + var fileData = Files.readAllBytes(Paths.get(imageFilePath)); + if (fileData == null) { + throw new IOException("invalid file"); + } + + // encrypt the image + var encryptResult = CryptTool.encrypt(fileData, this.privateKey, publicKey); + + // upload the image + var uploadResult = apiConnector.uploadFile(encryptResult); + + if (!uploadResult.isSuccess()) { + throw new IOException( + "could not upload file (upload response " + uploadResult.getResponseCode() + ")"); + } + + // send it + var imageMessage = + CryptTool.encryptImageMessage(encryptResult, uploadResult, privateKey, publicKey); + + return apiConnector.sendE2EMessage( + threemaId, imageMessage.getNonce(), imageMessage.getResult()); + } + + /** + * Encrypt a file message and send it to the given recipient. The thumbnailMessagePath can be + * null. + * + * @param threemaId target Threema ID + * @param fileMessageFile the file to be sent + * @param thumbnailMessagePath file for thumbnail; if not set, no thumbnail will be sent + * @return generated message ID + * @throws InvalidKeyException + * @throws IOException + * @throws NotAllowedException + */ + public String sendFileMessage(String threemaId, File fileMessageFile, File thumbnailMessagePath) + throws InvalidKeyException, IOException, NotAllowedException { + // fetch public key + var publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + + // check capability of a key + var capabilityResult = this.apiConnector.lookupKeyCapability(threemaId); + if (capabilityResult == null || !capabilityResult.canImage()) { + throw new NotAllowedException(); + } + + if (!fileMessageFile.isFile()) { + throw new IOException("invalid file"); + } + + var fileData = this.readFile(fileMessageFile); + + if (fileData == null) { + throw new IOException("invalid file"); + } + + // encrypt the image + var encryptResult = CryptTool.encryptFileData(fileData); + + // upload the image + var uploadResult = apiConnector.uploadFile(encryptResult); + + if (!uploadResult.isSuccess()) { + throw new IOException( + "could not upload file (upload response " + uploadResult.getResponseCode() + ")"); + } + + UploadResult uploadResultThumbnail = null; + + if (thumbnailMessagePath != null && thumbnailMessagePath.isFile()) { + var thumbnailData = this.readFile(thumbnailMessagePath); + if (thumbnailData == null) { + throw new IOException("invalid thumbnail file"); + } + + // encrypt the thumbnail + var encryptResultThumbnail = + CryptTool.encryptFileThumbnailData(fileData, encryptResult.getSecret()); + + // upload the thumbnail + uploadResultThumbnail = this.apiConnector.uploadFile(encryptResultThumbnail); + } + + // send it + var fileMessage = + CryptTool.encryptFileMessage( + encryptResult, + uploadResult, + Files.probeContentType(fileMessageFile.toPath()), + fileMessageFile.getName(), + (int) fileMessageFile.length(), + uploadResultThumbnail, + privateKey, + publicKey); + + return this.apiConnector.sendE2EMessage( + threemaId, fileMessage.getNonce(), fileMessage.getResult()); + } + + /** + * Decrypt a Message and download the blobs of the Message (e.g. image or file) + * + * @param threemaId Threema ID of the sender + * @param messageId Message ID + * @param box Encrypted box data of the file/image message + * @param nonce Nonce that was used for message encryption + * @param outputFolder Output folder for storing decrypted images/files + * @return result of message reception + * @throws IOException + * @throws InvalidKeyException + * @throws MessageParseException + */ + public ReceiveMessageResult receiveMessage( + String threemaId, String messageId, byte[] box, byte[] nonce, Path outputFolder) + throws IOException, InvalidKeyException, MessageParseException { + // fetch public key + var publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + + var message = CryptTool.decryptMessage(box, this.privateKey, publicKey, nonce); + if (message == null) { + return null; + } + + var result = new ReceiveMessageResult(messageId, message); + + if (message instanceof ImageMessage imageMessage) { + var fileData = this.apiConnector.downloadFile(imageMessage.getBlobId()); + + if (fileData == null) { + throw new MessageParseException(); + } + + var decryptedFileContent = + CryptTool.decrypt(fileData, privateKey, publicKey, imageMessage.getNonce()); + var imageFile = new File(outputFolder.toString() + "/" + messageId + ".jpg"); + var fos = new FileOutputStream(imageFile); + fos.write(decryptedFileContent); + fos.close(); + + result.files.add(imageFile); + } else if (message instanceof FileMessage fileMessage) { + var fileData = this.apiConnector.downloadFile(fileMessage.getBlobId()); + + var decryptedFileData = CryptTool.decryptFileData(fileData, fileMessage.getEncryptionKey()); + var file = + new File(outputFolder.toString() + "/" + messageId + "-" + fileMessage.getFileName()); + var fos = new FileOutputStream(file); + fos.write(decryptedFileData); + fos.close(); + + result.files.add(file); + + if (fileMessage.getThumbnailBlobId() != null) { + var thumbnailData = this.apiConnector.downloadFile(fileMessage.getThumbnailBlobId()); + + var decryptedThumbnailData = + CryptTool.decryptFileThumbnailData(thumbnailData, fileMessage.getEncryptionKey()); + var thumbnailFile = new File(outputFolder.toString() + "/" + messageId + "-thumbnail.jpg"); + fos = new FileOutputStream(thumbnailFile); + fos.write(decryptedThumbnailData); + fos.close(); + + result.files.add(thumbnailFile); + } + } + + return result; + } + + /** + * Read file data from file - store at offset in byte array for in-place encryption + * + * @param file input file + * @return file data with padding/offset for NaCl + * @throws IOException + */ + private byte[] readFile(File file) throws IOException { + var fileLength = (int) file.length(); + var fileData = new byte[fileLength + NaCl.BOXOVERHEAD]; + IOUtils.readFully(new FileInputStream(file), fileData, NaCl.BOXOVERHEAD, fileLength); + return fileData; + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8aa1797 --- /dev/null +++ b/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + net.klesatschke.threema + msgapi-sdk-java + 1.1.5-SNAPSHOT + Threema Messaging SDK + pom + + UTF-8 + ${source.encoding} + ${source.encoding} + 1.18.22 + + + + + org.junit.jupiter + junit-jupiter-api + 5.9.1 + test + + + org.junit.jupiter + junit-jupiter-params + 5.9.1 + test + + + org.assertj + assertj-core + 3.23.1 + test + + + org.mockito + mockito-junit-jupiter + 4.8.1 + test + + + org.mock-server + mockserver-junit-jupiter-no-dependencies + 5.14.0 + test + + + org.mock-server + mockserver-client-java-no-dependencies + 5.14.0 + test + + + org.projectlombok + lombok + ${lombok.version} + + + commons-io + commons-io + 2.10.0 + + + org.apache.commons + commons-text + 1.10.0 + + + com.google.code.gson + gson + 2.8.6 + + + eu.neilalexander + jnacl + 1.0.1 + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + ${source.encoding} + 17 + 17 + + + org.projectlombok + lombok + ${lombok.version} + + + + + + + + api + cli + + \ No newline at end of file diff --git a/source/src/main/java/ch/threema/apitool/APIConnector.java b/source/src/main/java/ch/threema/apitool/APIConnector.java deleted file mode 100644 index a310191..0000000 --- a/source/src/main/java/ch/threema/apitool/APIConnector.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -import ch.threema.apitool.results.CapabilityResult; -import ch.threema.apitool.results.EncryptResult; -import ch.threema.apitool.results.UploadResult; - -import javax.net.ssl.HttpsURLConnection; -import java.io.*; -import java.net.URL; -import java.net.URLEncoder; -import java.security.SecureRandom; -import java.util.HashMap; -import java.util.Map; - -/** - * Facilitates HTTPS communication with the Threema Message API. - */ -public class APIConnector { - private static final int BUFFER_SIZE = 16384; - - public interface ProgressListener { - - /** - * Update the progress of an upload/download process. - * - * @param progress in percent (0..100) - */ - void updateProgress(int progress); - } - - public class InputStreamLength { - public final InputStream inputStream; - public final int length; - - public InputStreamLength(InputStream inputStream, int length) { - this.inputStream = inputStream; - this.length = length; - } - } - - private final String apiUrl; - private final PublicKeyStore publicKeyStore; - private final String apiIdentity; - private final String secret; - - public APIConnector(String apiIdentity, String secret, PublicKeyStore publicKeyStore) { - this(apiIdentity, secret, "https://msgapi.threema.ch/", publicKeyStore); - } - - public APIConnector(String apiIdentity, String secret, String apiUrl, PublicKeyStore publicKeyStore) { - this.apiIdentity = apiIdentity; - this.secret = secret; - this.apiUrl = apiUrl; - this.publicKeyStore = publicKeyStore; - } - - /** - * Send a text message with server-side encryption. - * - * @param to recipient ID - * @param text message text (max. 3500 bytes) - * @return message ID - * @throws IOException if a communication or server error occurs - */ - public String sendTextMessageSimple(String to, String text) throws IOException { - - Map postParams = makeRequestParams(); - postParams.put("to", to); - postParams.put("text", text); - - return doPost(new URL(this.apiUrl + "send_simple"), postParams); - } - - /** - * Send an end-to-end encrypted message. - * - * @param to recipient ID - * @param nonce nonce used for encryption (24 bytes) - * @param box encrypted message data (max. 4000 bytes) - * @return message ID - * @throws IOException if a communication or server error occurs - */ - public String sendE2EMessage(String to, byte[] nonce, byte[] box) throws IOException { - - Map postParams = makeRequestParams(); - postParams.put("to", to); - postParams.put("nonce", DataUtils.byteArrayToHexString(nonce)); - postParams.put("box", DataUtils.byteArrayToHexString(box)); - - return doPost(new URL(this.apiUrl + "send_e2e"), postParams); - } - - /** - * Lookup an ID by phone number. The phone number will be hashed before - * being sent to the server. - * - * @param phoneNumber the phone number in E.164 format - * @return the ID, or null if not found - * @throws IOException if a communication or server error occurs - */ - public String lookupPhone(String phoneNumber) throws IOException { - - try { - Map getParams = makeRequestParams(); - - byte[] phoneHash = CryptTool.hashPhoneNo(phoneNumber); - - return doGet(new URL(this.apiUrl + "lookup/phone_hash/" + DataUtils.byteArrayToHexString(phoneHash)), getParams); - } catch (FileNotFoundException e) { - return null; - } - } - - /** - * Lookup an ID by email address. The email address will be hashed before - * being sent to the server. - * - * @param email the email address - * @return the ID, or null if not found - * @throws IOException if a communication or server error occurs - */ - public String lookupEmail(String email) throws IOException { - - try { - Map getParams = makeRequestParams(); - - byte[] emailHash = CryptTool.hashEmail(email); - - return doGet(new URL(this.apiUrl + "lookup/email_hash/" + DataUtils.byteArrayToHexString(emailHash)), getParams); - } catch (FileNotFoundException e) { - return null; - } - } - - /** - * Lookup a public key by ID. - * - * @param id the ID whose public key is desired - * @return the corresponding public key, or null if not found - * @throws IOException if a communication or server error occurs - */ - public byte[] lookupKey(String id) throws IOException { - byte[] key = this.publicKeyStore.getPublicKey(id); - if(key == null) { - try { - Map getParams = makeRequestParams(); - String pubkeyHex = doGet(new URL(this.apiUrl + "pubkeys/" + id), getParams); - key = DataUtils.hexStringToByteArray(pubkeyHex); - - if(key != null) { - this.publicKeyStore.save(id, key); - } - } catch (FileNotFoundException e) { - return null; - } - } - return key; - } - - /** - * Lookup the capabilities of a ID - * - * @param threemaId The ID whose capabilities should be checked - * @return The capabilities, or null if not found - * @throws IOException - */ - public CapabilityResult lookupKeyCapability(String threemaId) throws IOException { - String res = doGet(new URL(this.apiUrl + "capabilities/" + threemaId), - makeRequestParams()); - if(res != null) { - return new CapabilityResult(threemaId, res.split(",")); - } - return null; - } - - public Integer lookupCredits() throws IOException { - String res = doGet(new URL(this.apiUrl + "credits"), - makeRequestParams()); - if(res != null) { - return Integer.valueOf(res); - } - return null; - } - /** - * Upload a file. - * - * @param fileEncryptionResult The result of the file encryption (i.e. encrypted file data) - * @return the result of the upload - * @throws IOException - */ - public UploadResult uploadFile(EncryptResult fileEncryptionResult) throws IOException{ - - String attachmentName = "blob"; - String attachmentFileName = "blob.file"; - String crlf = "\r\n"; - String twoHyphens = "--"; - - char[] chars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); - String boundary = ""; - SecureRandom rand = new SecureRandom(); - int count = rand.nextInt(11) + 30; - for (int i = 0; i < count; i++) { - boundary += chars[rand.nextInt(chars.length)]; - } - - - String queryString = makeUrlEncoded(makeRequestParams()); - URL url = new URL(this.apiUrl + "upload_blob?" + queryString); - - HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); - connection.setDoOutput(true); - connection.setDoInput(true); - connection.setUseCaches(false); - - connection.setRequestMethod("POST"); - connection.setRequestProperty("Connection", "Keep-Alive"); - connection.setRequestProperty("Cache-Control", "no-cache"); - connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); - - DataOutputStream request = new DataOutputStream(connection.getOutputStream()); - - request.writeBytes(twoHyphens + boundary + crlf); - request.writeBytes("Content-Disposition: form-data; name=\"" + attachmentName + "\";filename=\"" + attachmentFileName + "\"" + crlf); - request.writeBytes(crlf); - request.write(fileEncryptionResult.getResult()); - request.writeBytes(crlf); - request.writeBytes(twoHyphens + boundary + twoHyphens + crlf); - - String response = null; - int responseCode = connection.getResponseCode(); - - if(responseCode == 200) { - InputStream is = connection.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - response = br.readLine(); - br.close(); - } - - connection.disconnect(); - - return new UploadResult(responseCode, response != null ? DataUtils.hexStringToByteArray(response) : null); - } - - /** - * Download a file given its blob ID. - * - * @param blobId The blob ID of the file - * @return Encrypted file data - * @throws IOException - */ - public byte[] downloadFile(byte[] blobId) throws IOException { - return this.downloadFile(blobId, null); - } - - /** - * Download a file given its blob ID. - * - * @param blobId The blob ID of the file - * @param progressListener An object that will receive progress information, or null - * @return Encrypted file data - * @throws IOException - */ - public byte[] downloadFile(byte[] blobId, ProgressListener progressListener) throws IOException { - String queryString = makeUrlEncoded(makeRequestParams()); - URL blobUrl = new URL(String.format(this.apiUrl + "blobs/%s?%s", - DataUtils.byteArrayToHexString(blobId), - queryString)); - - HttpsURLConnection connection = (HttpsURLConnection)blobUrl.openConnection(); - connection.setConnectTimeout(20*1000); - connection.setReadTimeout(20*1000); - connection.setDoOutput(false); - - InputStream inputStream = connection.getInputStream(); - int contentLength = connection.getContentLength(); - InputStreamLength isl = new InputStreamLength(inputStream, contentLength); - - - /* Content length known? */ - byte[] blob; - if (isl.length != -1) { - blob = new byte[isl.length]; - int offset = 0; - int readed; - - while (offset < isl.length && (readed = isl.inputStream.read(blob, offset, isl.length - offset)) != -1) { - offset += readed; - - if (progressListener != null) { - progressListener.updateProgress(100 * offset / isl.length); - } - } - - if (offset != isl.length) { - throw new IOException("Unexpected read size. current: " + offset + ", excepted: " + isl.length); - } - } else { - /* Content length is unknown - need to read until EOF */ - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buffer = new byte[BUFFER_SIZE]; - - int read; - while ((read = isl.inputStream.read(buffer)) != -1) { - bos.write(buffer, 0, read); - } - - blob = bos.toByteArray(); - } - if (progressListener != null) { - progressListener.updateProgress(100); - } - - return blob; - } - - private Map makeRequestParams() { - Map postParams = new HashMap(); - - postParams.put("from", apiIdentity); - postParams.put("secret", secret); - return postParams; - } - - private String doGet(URL url, Map getParams) throws IOException { - - if (getParams != null) { - String queryString = makeUrlEncoded(getParams); - - url = new URL(url.toString() + "?" + queryString); - } - - HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); - connection.setDoOutput(false); - connection.setDoInput(true); - connection.setInstanceFollowRedirects(false); - connection.setRequestMethod("GET"); - connection.setUseCaches(false); - - InputStream is = connection.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String response = br.readLine(); - br.close(); - - connection.disconnect(); - - return response; - } - - private String doPost(URL url, Map postParams) throws IOException { - - byte[] postData = makeUrlEncoded(postParams).getBytes("UTF-8"); - - HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); - connection.setDoOutput(true); - connection.setDoInput(true); - connection.setInstanceFollowRedirects(false); - connection.setRequestMethod("POST"); - connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - connection.setRequestProperty("Charset", "utf-8"); - connection.setRequestProperty("Content-Length", Integer.toString(postData.length)); - connection.setUseCaches(false); - - OutputStream os = connection.getOutputStream(); - os.write(postData); - os.flush(); - os.close(); - - InputStream is = connection.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String response = br.readLine(); - br.close(); - - connection.disconnect(); - - return response; - } - - private String makeUrlEncoded(Map params) { - StringBuilder s = new StringBuilder(); - - for (Map.Entry param : params.entrySet()) { - if (s.length() > 0) - s.append('&'); - - s.append(param.getKey()); - s.append('='); - try { - s.append(URLEncoder.encode(param.getValue(), "UTF-8")); - } catch (UnsupportedEncodingException ignored) {} - } - - return s.toString(); - } - -} diff --git a/source/src/main/java/ch/threema/apitool/ConsoleMain.java b/source/src/main/java/ch/threema/apitool/ConsoleMain.java deleted file mode 100644 index fff8668..0000000 --- a/source/src/main/java/ch/threema/apitool/ConsoleMain.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ -package ch.threema.apitool; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringEscapeUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.text.WordUtils; - -import ch.threema.apitool.console.commands.CapabilityCommand; -import ch.threema.apitool.console.commands.Command; -import ch.threema.apitool.console.commands.CreditsCommand; -import ch.threema.apitool.console.commands.DecryptAndDownloadCommand; -import ch.threema.apitool.console.commands.DecryptCommand; -import ch.threema.apitool.console.commands.DerivePublicKeyCommand; -import ch.threema.apitool.console.commands.EncryptCommand; -import ch.threema.apitool.console.commands.FetchPublicKey; -import ch.threema.apitool.console.commands.GenerateKeyPairCommand; -import ch.threema.apitool.console.commands.HashEmailCommand; -import ch.threema.apitool.console.commands.HashPhoneCommand; -import ch.threema.apitool.console.commands.IDLookupByEmail; -import ch.threema.apitool.console.commands.IDLookupByPhoneNo; -import ch.threema.apitool.console.commands.SendE2EFileMessageCommand; -import ch.threema.apitool.console.commands.SendE2EImageMessageCommand; -import ch.threema.apitool.console.commands.SendE2ETextMessageCommand; -import ch.threema.apitool.console.commands.SendSimpleMessageCommand; - -/** - * Command line interface for {@link CryptTool} and {@link APIConnector} operations - * for testing purposes and simple invocation from other programming languages. - */ -public class ConsoleMain { - - static class Commands { - protected final List commandGroups = new ArrayList<>(); - - public CommandGroup create(String description) { - CommandGroup g = new CommandGroup(description); - this.commandGroups.add(g); - return g; - } - - public ArgumentCommand find(String... arguments) { - if (arguments.length > 0) { - for (CommandGroup g : this.commandGroups) { - ArgumentCommand c = g.find(arguments); - if (c != null) { - return c; - } - } - } - return null; - } - } - - static class CommandGroup { - protected final String description; - protected List argumentCommands = new ArrayList<>(); - - CommandGroup(String description) { - this.description = description; - } - - public CommandGroup add(Command command, String... arguments) { - this.argumentCommands.add(new ArgumentCommand(arguments, command)); - return this; - } - - public ArgumentCommand find(String... arguments) { - ArgumentCommand matchedArgumentCommand = null; - int argMatchedSize = -1; - for (ArgumentCommand c : this.argumentCommands) { - boolean matched = true; - int matchedSize = 0; - for (int n = 0; n < c.arguments.length; n++) { - if (n > arguments.length || !c.arguments[n].equals(arguments[n])) { - matched = false; - break; - } else { - matchedSize++; - } - } - - if (matched && matchedSize > argMatchedSize) { - matchedArgumentCommand = c; - argMatchedSize = matchedSize; - } - - } - return matchedArgumentCommand; - } - } - - static class ArgumentCommand { - protected final String[] arguments; - protected final Command command; - - ArgumentCommand(String[] arguments, Command command) { - this.arguments = arguments; - this.command = command; - } - - public void run(String[] givenArguments) throws Exception { - if (givenArguments.length < this.arguments.length) { - throw new Exception("invalid arguments"); - } - - this.command - .run((String[]) ArrayUtils.subarray(givenArguments, this.arguments.length, givenArguments.length)); - } - } - - private static final Commands commands = new Commands(); - - public static void main(String[] args) throws Exception { - - commands.create("Local operations (no network communication)").add(new EncryptCommand(), "-e") - .add(new DecryptCommand(), "-d").add(new HashEmailCommand(), "-h", "-e") - .add(new HashPhoneCommand(), "-h", "-p").add(new GenerateKeyPairCommand(), "-g") - .add(new DerivePublicKeyCommand(), "-p"); - - commands.create("Network operations").add(new SendSimpleMessageCommand(), "-s") - .add(new SendE2ETextMessageCommand(), "-S").add(new SendE2EImageMessageCommand(), "-S", "-i") - .add(new SendE2EFileMessageCommand(), "-S", "-f").add(new IDLookupByEmail(), "-l", "-e") - .add(new IDLookupByPhoneNo(), "-l", "-p").add(new FetchPublicKey(), "-l", "-k") - .add(new CapabilityCommand(), "-c").add(new DecryptAndDownloadCommand(), "-D") - .add(new CreditsCommand(), "-C"); - - ArgumentCommand argumentCommand = commands.find(args); - if (argumentCommand == null) { - usage(args.length == 1 && args[0].equals("html")); - } else { - argumentCommand.run(args); - } - } - - private static void usage(boolean htmlOutput) { - if (!htmlOutput) { - System.out.println("version:" + ConsoleMain.class.getPackage().getImplementationVersion()); - - System.out.println("usage:\n"); - - System.out.println("General information"); - System.out.println("-------------------\n"); - - System.out.println("Where a key needs to be specified, it can either be given directly as"); - System.out.println("a command line parameter (in hex with a prefix indicating the type;"); - System.out.println("not recommended on shared machines as other users may be able to see"); - System.out.println("the arguments), or as the path to a file that it should be read from"); - System.out.println("(file contents also in hex with the prefix).\n"); - } - - String groupDescriptionTemplate = htmlOutput ? "

%s

\n" - : "\n%s\n" + StringUtils.repeat("-", 80) + "\n\n"; - String commandTemplate = htmlOutput ? "
java -jar threema-msgapi-tool.jar %s
\n" : "%s\n"; - - for (CommandGroup commandGroup : commands.commandGroups) { - System.out.format(groupDescriptionTemplate, commandGroup.description); - - for (ArgumentCommand argumentCommand : commandGroup.argumentCommands) { - StringBuilder command = new StringBuilder(); - for (int n = 0; n < argumentCommand.arguments.length; n++) { - command.append(argumentCommand.arguments[n]).append(" "); - } - String argumentDescription = argumentCommand.command.getUsageArguments(); - if (htmlOutput) { - System.out.format("

%s

\n", argumentCommand.command.getSubject()); - argumentDescription = StringEscapeUtils.escapeHtml3(argumentDescription); - } - command.append(argumentDescription); - - System.out.format(commandTemplate, command.toString().trim()); - - String description = argumentCommand.command.getUsageDescription(); - if (htmlOutput) { - System.out.format("

%s

\n\n", description); - } else { - System.out.println(" " + WordUtils.wrap(description, 76, "\n ", false)); - System.out.println(""); - } - } - } - } -} diff --git a/source/src/main/java/ch/threema/apitool/CryptTool.java b/source/src/main/java/ch/threema/apitool/CryptTool.java deleted file mode 100644 index 688a798..0000000 --- a/source/src/main/java/ch/threema/apitool/CryptTool.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -import ch.threema.apitool.exceptions.BadMessageException; -import ch.threema.apitool.exceptions.DecryptionFailedException; -import ch.threema.apitool.exceptions.MessageParseException; -import ch.threema.apitool.exceptions.UnsupportedMessageTypeException; -import ch.threema.apitool.messages.*; -import ch.threema.apitool.results.EncryptResult; -import ch.threema.apitool.results.UploadResult; -import com.neilalexander.jnacl.NaCl; -import org.apache.commons.io.EndianUtils; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.UnsupportedEncodingException; -import java.security.SecureRandom; -import java.util.LinkedList; -import java.util.List; - -/** - * Contains static methods to do various Threema cryptography related tasks. - */ -public class CryptTool { - - /* HMAC-SHA256 keys for email/mobile phone hashing */ - private static final byte[] EMAIL_HMAC_KEY = new byte[] {(byte)0x30,(byte)0xa5,(byte)0x50,(byte)0x0f,(byte)0xed,(byte)0x97,(byte)0x01,(byte)0xfa,(byte)0x6d,(byte)0xef,(byte)0xdb,(byte)0x61,(byte)0x08,(byte)0x41,(byte)0x90,(byte)0x0f,(byte)0xeb,(byte)0xb8,(byte)0xe4,(byte)0x30,(byte)0x88,(byte)0x1f,(byte)0x7a,(byte)0xd8,(byte)0x16,(byte)0x82,(byte)0x62,(byte)0x64,(byte)0xec,(byte)0x09,(byte)0xba,(byte)0xd7}; - private static final byte[] PHONENO_HMAC_KEY = new byte[] {(byte)0x85,(byte)0xad,(byte)0xf8,(byte)0x22,(byte)0x69,(byte)0x53,(byte)0xf3,(byte)0xd9,(byte)0x6c,(byte)0xfd,(byte)0x5d,(byte)0x09,(byte)0xbf,(byte)0x29,(byte)0x55,(byte)0x5e,(byte)0xb9,(byte)0x55,(byte)0xfc,(byte)0xd8,(byte)0xaa,(byte)0x5e,(byte)0xc4,(byte)0xf9,(byte)0xfc,(byte)0xd8,(byte)0x69,(byte)0xe2,(byte)0x58,(byte)0x37,(byte)0x07,(byte)0x23}; - - private static final byte[] FILE_NONCE = new byte[]{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}; - private static final byte[] FILE_THUMBNAIL_NONCE = new byte[]{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02}; - - private static final SecureRandom random = new SecureRandom(); - - /** - * Encrypt a text message. - * - * @param text the text to be encrypted (max. 3500 bytes) - * @param senderPrivateKey the private key of the sending ID - * @param recipientPublicKey the public key of the receiving ID - * @return encrypted result - */ - public static EncryptResult encryptTextMessage(String text, byte[] senderPrivateKey, byte[] recipientPublicKey) { - return encryptMessage(new TextMessage(text), senderPrivateKey, recipientPublicKey); - } - - - /** - * Encrypt an image message. - * - * @param encryptResult result of the image encryption - * @param uploadResult result of the upload - * @param senderPrivateKey the private key of the sending ID - * @param recipientPublicKey the public key of the receiving ID - * @return encrypted result - */ - public static EncryptResult encryptImageMessage(EncryptResult encryptResult, UploadResult uploadResult, byte[] senderPrivateKey, byte[] recipientPublicKey) { - return encryptMessage( - new ImageMessage(uploadResult.getBlobId(), - encryptResult.getSize(), - encryptResult.getNonce()), - senderPrivateKey, - recipientPublicKey); - } - - /** - * Encrypt a file message. - * - * @param encryptResult result of the file data encryption - * @param uploadResult result of the upload - * @param mimeType MIME type of the file - * @param fileName File name - * @param fileSize Size of the file, in bytes - * @param uploadResultThumbnail result of thumbnail upload - * @param senderPrivateKey Private key of sender - * @param recipientPublicKey Public key of recipient - * @return Result of the file message encryption (not the same as the file data encryption!) - */ - public static EncryptResult encryptFileMessage(EncryptResult encryptResult, - UploadResult uploadResult, - String mimeType, - String fileName, - int fileSize, - UploadResult uploadResultThumbnail, - byte[] senderPrivateKey, byte[] recipientPublicKey) { - return encryptMessage( - new FileMessage(uploadResult.getBlobId(), - encryptResult.getSecret(), - mimeType, - fileName, - fileSize, - uploadResultThumbnail != null ? uploadResultThumbnail.getBlobId() : null), - senderPrivateKey, - recipientPublicKey); - } - - - private static EncryptResult encryptMessage(ThreemaMessage threemaMessage, byte[] privateKey, byte[] publicKey) { - /* determine random amount of PKCS7 padding */ - int padbytes = random.nextInt(254) + 1; - - byte[] messageBytes; - try { - messageBytes = threemaMessage.getData(); - } catch (BadMessageException e) { - return null; - } - - /* prepend type byte (0x02) to message data */ - byte[] data = new byte[1 + messageBytes.length + padbytes]; - data[0] = (byte)threemaMessage.getTypeCode(); - - System.arraycopy(messageBytes, 0, data, 1, messageBytes.length); - - /* append padding */ - for (int i = 0; i < padbytes; i++) { - data[i + 1 + messageBytes.length] = (byte)padbytes; - } - - return encrypt(data, privateKey, publicKey); - } - - /** - * Decrypt an NaCl box using the recipient's private key and the sender's public key. - * - * @param box The box to be decrypted - * @param privateKey The private key of the recipient - * @param publicKey The public key of the sender - * @param nonce The nonce that was used for encryption - * @return The decrypted data, or null if decryption failed - */ - public static byte[] decrypt(byte[] box, byte[] privateKey, byte[] publicKey, byte[] nonce) { - return new NaCl(privateKey, publicKey).decrypt(box, nonce); - } - - /** - * Decrypt symmetrically encrypted file data. - * - * @param fileData The encrypted file data - * @param secret The symmetric key that was used for encryption - * @return The decrypted file data, or null if decryption failed - */ - public static byte[] decryptFileData(byte[] fileData, byte[] secret) { - return NaCl.symmetricDecryptData(fileData, secret, FILE_NONCE); - } - - /** - * Decrypt symmetrically encrypted file thumbnail data. - * - * @param fileData The encrypted thumbnail data - * @param secret The symmetric key that was used for encryption - * @return The decrypted thumbnail data, or null if decryption failed - */ - public static byte[] decryptFileThumbnailData(byte[] fileData, byte[] secret) { - return NaCl.symmetricDecryptData(fileData, secret, FILE_THUMBNAIL_NONCE); - } - - /** - * Decrypt a message. - * - * @param box the box to be decrypted - * @param recipientPrivateKey the private key of the receiving ID - * @param senderPublicKey the public key of the sending ID - * @param nonce the nonce that was used for the encryption - * @return decrypted message (text or delivery receipt) - */ - public static ThreemaMessage decryptMessage(byte[] box, byte[] recipientPrivateKey, byte[] senderPublicKey, byte[] nonce) throws MessageParseException { - - byte[] data = decrypt(box, recipientPrivateKey, senderPublicKey, nonce); - if (data == null) - throw new DecryptionFailedException(); - - /* remove padding */ - int padbytes = data[data.length-1] & 0xFF; - int realDataLength = data.length - padbytes; - if (realDataLength < 1) - throw new BadMessageException(); /* Bad message padding */ - - /* first byte of data is type */ - int type = data[0] & 0xFF; - - switch (type) { - case TextMessage.TYPE_CODE: - /* Text message */ - if (realDataLength < 2) - throw new BadMessageException(); - - try { - return new TextMessage(new String(data, 1, realDataLength - 1, "UTF-8")); - } catch (UnsupportedEncodingException e) { - /* should never happen, UTF-8 is always supported */ - throw new RuntimeException(e); - } - - case DeliveryReceipt.TYPE_CODE: - /* Delivery receipt */ - if (realDataLength < MessageId.MESSAGE_ID_LEN + 2 || ((realDataLength - 2) % MessageId.MESSAGE_ID_LEN) != 0) - throw new BadMessageException(); - - DeliveryReceipt.Type receiptType = DeliveryReceipt.Type.get(data[1] & 0xFF); - if (receiptType == null) - throw new BadMessageException(); - - List messageIds = new LinkedList(); - - int numMsgIds = ((realDataLength - 2) / MessageId.MESSAGE_ID_LEN); - for (int i = 0; i < numMsgIds; i++) { - messageIds.add(new MessageId(data, 2 + i*MessageId.MESSAGE_ID_LEN)); - } - - return new DeliveryReceipt(receiptType, messageIds); - - case ImageMessage.TYPE_CODE: - if(realDataLength != (1 + ThreemaMessage.BLOB_ID_LEN + 4 + NaCl.NONCEBYTES)) { - System.out.println(String.valueOf(realDataLength)); - System.out.println(String.valueOf(1 + ThreemaMessage.BLOB_ID_LEN + 4 + NaCl.NONCEBYTES)); - throw new BadMessageException(); - } - byte[] blobId = new byte[ThreemaMessage.BLOB_ID_LEN]; - System.arraycopy(data, 1, blobId, 0, ThreemaMessage.BLOB_ID_LEN); - int size = EndianUtils.readSwappedInteger(data, 1 + ThreemaMessage.BLOB_ID_LEN); - byte[] fileNonce = new byte[NaCl.NONCEBYTES]; - System.arraycopy(data, 1 + 4 + ThreemaMessage.BLOB_ID_LEN, fileNonce, 0, nonce.length); - - return new ImageMessage(blobId, size, fileNonce); - - case FileMessage.TYPE_CODE: - try { - return FileMessage.fromString(new String(data, 1, realDataLength-1, "UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new BadMessageException(); - } - - default: - throw new UnsupportedMessageTypeException(); - } - } - - /** - * Generate a new key pair. - * - * @param privateKey is used to return the generated private key (length must be NaCl.PRIVATEKEYBYTES) - * @param publicKey is used to return the generated public key (length must be NaCl.PUBLICKEYBYTES) - */ - public static void generateKeyPair(byte[] privateKey, byte[] publicKey) { - if (publicKey.length != NaCl.PUBLICKEYBYTES || privateKey.length != NaCl.SECRETKEYBYTES) { - throw new IllegalArgumentException("Wrong key length"); - } - - NaCl.genkeypair(publicKey, privateKey); - } - - /** - * Encrypt data using NaCl asymmetric ("box") encryption. - * - * @param data the data to be encrypted - * @param privateKey is used to return the generated private key (length must be NaCl.PRIVATEKEYBYTES) - * @param publicKey is used to return the generated public key (length must be NaCl.PUBLICKEYBYTES) - */ - public static EncryptResult encrypt(byte[] data,byte[] privateKey, byte[] publicKey) { - if (publicKey.length != NaCl.PUBLICKEYBYTES || privateKey.length != NaCl.SECRETKEYBYTES) { - throw new IllegalArgumentException("Wrong key length"); - } - - byte[] nonce = randomNonce(); - NaCl naCl = new NaCl(privateKey, publicKey); - return new EncryptResult(naCl.encrypt(data, nonce), null, nonce); - } - - /** - * Encrypt file data using NaCl symmetric encryption with a random key. - * - * @param data the file contents to be encrypted - * @return the encryption result including the random key - */ - public static EncryptResult encryptFileData(byte[] data) { - //create random key - SecureRandom rnd = new SecureRandom(); - byte[] encryptionKey = new byte[NaCl.SYMMKEYBYTES]; - rnd.nextBytes(encryptionKey); - - //encrypt file data in-place - NaCl.symmetricEncryptDataInplace(data, encryptionKey, FILE_NONCE); - - return new EncryptResult(data, encryptionKey, FILE_NONCE); - } - - /** - * Encrypt file thumbnail data using NaCl symmetric encryption with a random key. - * - * @param data the file contents to be encrypted - * @return the encryption result including the random key - */ - public static EncryptResult encryptFileThumbnailData(byte[] data, byte[] encryptionKey) { - // encrypt file data in-place - NaCl.symmetricEncryptDataInplace(data, encryptionKey, FILE_THUMBNAIL_NONCE); - - return new EncryptResult(data, encryptionKey, FILE_THUMBNAIL_NONCE); - } - - /** - * Hashes an email address for identity lookup. - * - * @param email the email address - * @return the raw hash - */ - public static byte[] hashEmail(String email) { - try { - Mac emailMac = Mac.getInstance("HmacSHA256"); - emailMac.init(new SecretKeySpec(EMAIL_HMAC_KEY, "HmacSHA256")); - String normalizedEmail = email.toLowerCase().trim(); - return emailMac.doFinal(normalizedEmail.getBytes("US-ASCII")); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - /** - * Hashes a phone number for identity lookup. - * - * @param phoneNo the phone number - * @return the raw hash - */ - public static byte[] hashPhoneNo(String phoneNo) { - try { - Mac phoneMac = Mac.getInstance("HmacSHA256"); - phoneMac.init(new SecretKeySpec(PHONENO_HMAC_KEY, "HmacSHA256")); - String normalizedPhoneNo = phoneNo.replaceAll("[^0-9]", ""); - return phoneMac.doFinal(normalizedPhoneNo.getBytes("US-ASCII")); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - /** - * Generate a random nonce. - * - * @return random nonce - */ - public static byte[] randomNonce() { - byte[] nonce = new byte[NaCl.NONCEBYTES]; - random.nextBytes(nonce); - return nonce; - } - - /** - * Return the public key that corresponds with a given private key. - * - * @param privateKey The private key whose public key should be derived - * @return The corresponding public key. - */ - public static byte[] derivePublicKey(byte[] privateKey) { - return NaCl.derivePublicKey(privateKey); - } -} diff --git a/source/src/main/java/ch/threema/apitool/DataUtils.java b/source/src/main/java/ch/threema/apitool/DataUtils.java deleted file mode 100644 index f9ffa68..0000000 --- a/source/src/main/java/ch/threema/apitool/DataUtils.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -import ch.threema.apitool.exceptions.InvalidKeyException; - -import java.io.*; - -public class DataUtils { - - /** - * Convert a string in hexadecimal representation to a byte array. - * - * @param s hex string - * @return decoded byte array - */ - public static byte[] hexStringToByteArray(String s) { - String sc = s.replaceAll("[^0-9a-fA-F]", ""); - int len = sc.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(sc.charAt(i), 16) << 4) - + Character.digit(sc.charAt(i+1), 16)); - } - return data; - } - - /** - * Convert a byte array into a hexadecimal string (lowercase). - * - * @param bytes the bytes to encode - * @return hex encoded string - */ - public static String byteArrayToHexString(byte[] bytes) { - final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; - char[] hexChars = new char[bytes.length * 2]; - int v; - for (int j = 0; j < bytes.length; j++) { - v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - - /** - * Read hexadecimal data from a file and return it as a byte array. - * - * @param inFile input file - * @return the decoded data - * @throws java.io.IOException - */ - public static byte[] readHexFile(File inFile) throws IOException { - BufferedReader br = new BufferedReader(new FileReader(inFile)); - byte[] data = hexStringToByteArray(br.readLine().trim()); - br.close(); - return data; - } - - /** - * Write a byte array into a file in hexadecimal format. - * - * @param outFile output file - * @param data the data to be written - */ - public static void writeHexFile(File outFile, byte[] data) throws IOException { - FileWriter fw = new FileWriter(outFile); - fw.write(byteArrayToHexString(data)); - fw.write('\n'); - fw.close(); - } - - /** - * Read an encoded key from a file and return it as a key instance. - * - * @param inFile input file - * @return the decoded key - * @throws java.io.IOException - */ - public static Key readKeyFile(File inFile) throws IOException, InvalidKeyException { - BufferedReader br = new BufferedReader(new FileReader(inFile)); - String encodedKey = br.readLine().trim(); - br.close(); - return Key.decodeKey(encodedKey); - } - - /** - * Read an encoded key from a file and return it as a key instance. - * - * @param inFile input file - * @param expectedKeyType validates the key type (private or public) - * @return the decoded key - * @throws java.io.IOException - */ - public static Key readKeyFile(File inFile, String expectedKeyType) throws IOException, InvalidKeyException { - BufferedReader br = new BufferedReader(new FileReader(inFile)); - String encodedKey = br.readLine().trim(); - br.close(); - return Key.decodeKey(encodedKey, expectedKeyType); - } - - /** - * Write an encoded key to a file - * Encoded key format: type:hex_key. - * - * @param outFile output file - * @param key a key that will be encoded and written to a file - */ - public static void writeKeyFile(File outFile, Key key) throws IOException { - FileWriter fw = new FileWriter(outFile); - fw.write(key.encode()); - fw.write('\n'); - fw.close(); - } -} diff --git a/source/src/main/java/ch/threema/apitool/Key.java b/source/src/main/java/ch/threema/apitool/Key.java deleted file mode 100644 index d88cd79..0000000 --- a/source/src/main/java/ch/threema/apitool/Key.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -import ch.threema.apitool.exceptions.InvalidKeyException; - -/** - * Encapsulates an asymmetric key, either public or private. - */ -public class Key { - public static final String separator = ":"; - - public static class KeyType { - public static final String PRIVATE = "private"; - public static final String PUBLIC = "public"; - } - - /* Attributes */ - public byte[] key; - public String type; - - public Key(String type, byte[] key) { - this.key = key; - this.type = type; - } - - /** - * Decodes and validates an encoded key. - * Encoded key format: type:hex_key - * - * @param encodedKey an encoded key - * @throws ch.threema.apitool.exceptions.InvalidKeyException - */ - public static Key decodeKey(String encodedKey) throws InvalidKeyException { - // Split key and check length - String[] keyArray = encodedKey.split(Key.separator); - if (keyArray.length != 2) { - throw new InvalidKeyException("Does not contain a valid key format"); - } - - // Unpack key - String keyType = keyArray[0]; - String keyContent = keyArray[1]; - - // Is this a valid hex key? - if (!keyContent.matches("[0-9a-fA-F]{64}")) { - throw new InvalidKeyException("Does not contain a valid key"); - } - - return new Key(keyType, DataUtils.hexStringToByteArray(keyContent)); - } - - /** - * Decodes and validates an encoded key. - * Encoded key format: type:hex_key - * - * @param encodedKey an encoded key - * @param expectedKeyType the expected type of the key - * @throws InvalidKeyException - */ - public static Key decodeKey(String encodedKey, String expectedKeyType) throws InvalidKeyException { - Key key = decodeKey(encodedKey); - - // Check key type - if (!key.type.equals(expectedKeyType)) { - throw new InvalidKeyException("Expected key type: " + expectedKeyType + ", got: " + key.type); - } - - return key; - } - - /** - * Encodes a key. - * - * @return an encoded key - */ - public String encode() { - return this.type + Key.separator + DataUtils.byteArrayToHexString(this.key); - } -} diff --git a/source/src/main/java/ch/threema/apitool/PublicKeyStore.java b/source/src/main/java/ch/threema/apitool/PublicKeyStore.java deleted file mode 100644 index 741a0a0..0000000 --- a/source/src/main/java/ch/threema/apitool/PublicKeyStore.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -import java.util.HashMap; -import java.util.Map; - -/** - * Stores and caches public keys for Threema users. Extend this class to provide your - * own storage implementation, e.g. in a file or database. - */ -public abstract class PublicKeyStore { - private final Map cache = new HashMap<>(); - - /** - * Get the public key for a given Threema ID. The cache is checked first; if it - * is not found in the cache, fetchPublicKey() is called. - * - * @param threemaId The Threema ID whose public key should be obtained - * @return The public key, or null if not found. - */ - public final byte[] getPublicKey(String threemaId) { - synchronized (this.cache) { - byte[] pk = this.cache.get(threemaId); - - if (pk == null) { - pk = this.fetchPublicKey(threemaId); - this.cache.put(threemaId, pk); - } - return pk; - } - - } - - /** - * Store the public key for a given Threema ID in the cache, and the underlying store. - * - * @param threemaId The Threema ID whose public key should be stored - * @param publicKey The corresponding public key. - */ - public final void setPublicKey(String threemaId, byte[] publicKey) { - if(publicKey != null) { - synchronized (this.cache) { - this.cache.put(threemaId, publicKey); - this.save(threemaId, publicKey); - } - } - } - - /** - * Fetch the public key for the given Threema ID from the store. Override to provide - * your own implementation to read from the store. - * - * @param threemaId The Threema ID whose public key should be obtained - * @return The public key, or null if not found. - */ - abstract protected byte[] fetchPublicKey(String threemaId); - - /** - * Save the public key for a given Threema ID in the store. Override to provide - * your own implementation to write to the store. - * - * @param threemaId The Threema ID whose public key should be stored - * @param publicKey The corresponding public key. - */ - abstract protected void save(String threemaId, byte[] publicKey); -} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/Command.java b/source/src/main/java/ch/threema/apitool/console/commands/Command.java deleted file mode 100644 index f8e136f..0000000 --- a/source/src/main/java/ch/threema/apitool/console/commands/Command.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.console.commands; - -import ch.threema.apitool.APIConnector; -import ch.threema.apitool.PublicKeyStore; -import ch.threema.apitool.console.commands.fields.*; - -import java.io.*; -import java.util.LinkedList; -import java.util.List; - -abstract public class Command { - private final List fields = new LinkedList<>(); - private final String subject; - private final String description; - - public Command(String subject, String description) { - this.subject = subject; - this.description = description; - } - - private void addField(Field f) { - if(f.isRequired()) { - int pos = this.fields.size(); - //add after last required - for(int n = 0; n < this.fields.size(); n++) { - if(!this.fields.get(n).isRequired()) { - pos = n; - break; - } - } - this.fields.add(pos, f); - } - else { - this.fields.add(f); - } - } - protected TextField createTextField(String key) { - return this.createTextField(key, true); - } - protected TextField createTextField(String key, boolean required) { - TextField field = new TextField(key, required); - this.addField(field); - return field; - } - - protected ThreemaIDField createThreemaId(String key) { - return this.createThreemaId(key, true); - } - - protected ThreemaIDField createThreemaId(String key, boolean required) { - ThreemaIDField field = new ThreemaIDField(key, required); - this.addField(field); - return field; - } - - protected PublicKeyField createPublicKeyField(String key) { - return this.createPublicKeyField(key, true); - } - - protected PublicKeyField createPublicKeyField(String key, boolean required) { - PublicKeyField field = new PublicKeyField(key, required); - this.addField(field); - return field; - } - - protected PrivateKeyField createPrivateKeyField(String key) { - return this.createPrivateKeyField(key, true); - } - - protected PrivateKeyField createPrivateKeyField(String key, boolean required) { - PrivateKeyField field = new PrivateKeyField(key, required); - this.addField(field); - return field; - } - - protected FileField createFileField(String key) { - return this.createFileField(key, true); - } - - protected FileField createFileField(String key, boolean required) { - FileField field = new FileField(key, required); - this.addField(field); - return field; - } - - protected FolderField createFolderField(String key) { - return this.createFolderField(key, true); - } - - protected FolderField createFolderField(String key, boolean required) { - FolderField field = new FolderField(key, required); - this.addField(field); - return field; - } - - protected ByteArrayField createByteArrayField(String key) { - return this.createByteArrayField(key, true); - } - - protected ByteArrayField createByteArrayField(String key, boolean required) { - ByteArrayField field = new ByteArrayField(key, required); - this.addField(field); - return field; - } - - - protected APIConnector createConnector(String gatewayId, String secret) { - return new APIConnector(gatewayId, secret, new PublicKeyStore() { - @Override - protected byte[] fetchPublicKey(String threemaId) { - return null; - } - - @Override - protected void save(String threemaId, byte[] publicKey) { - //do nothing - } - }); - } - - protected String readStream(InputStream stream, String charset) throws IOException { - try { - Reader reader = new BufferedReader(new InputStreamReader(stream, charset)); - StringBuilder builder = new StringBuilder(); - char[] buffer = new char[8192]; - int read; - while ((read = reader.read(buffer, 0, buffer.length)) > 0) { - builder.append(buffer, 0, read); - } - return builder.toString(); - } finally { - stream.close(); - } - } - - public final void run(String[] arguments) throws Exception { - int pos = 0; - for(Field f: this.fields) { - if(arguments.length > pos) { - f.setValue(arguments[pos]); - } - pos++; - } - - //validate - for(Field f: this.fields) { - if(!f.isValid()) { - return; - } - } - - this.execute(); - } - - public final String getSubject() { - return this.subject; - } - - public final String getUsageArguments() { - StringBuilder usage = new StringBuilder(); - for(Field f: this.fields) { - usage.append(" ") - .append(f.isRequired() ? "<" : "[") - .append(f.getKey()) - .append(f.isRequired() ? ">" : "]"); - } - return usage.toString().trim(); - } - - public final String getUsageDescription() { - return this.description; - } - - protected abstract void execute() throws Exception; -} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/DecryptAndDownloadCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/DecryptAndDownloadCommand.java deleted file mode 100644 index 2dd42f4..0000000 --- a/source/src/main/java/ch/threema/apitool/console/commands/DecryptAndDownloadCommand.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.console.commands; - -import ch.threema.apitool.DataUtils; -import ch.threema.apitool.console.commands.fields.*; -import ch.threema.apitool.helpers.E2EHelper; - -import java.nio.file.Path; - -public class DecryptAndDownloadCommand extends Command { - private final ThreemaIDField threemaId; - private final ThreemaIDField fromField; - private final TextField secretField; - private final PrivateKeyField privateKeyField; - private final ByteArrayField nonceField; - private final FolderField outputFolderField; - private final TextField messageIdField; - - public DecryptAndDownloadCommand() { - super("Decrypt and download", - "Decrypt a box (box from the stdin) message and download (if the message is a image or file message) the file(s) to the defined directory" - ); - this.threemaId = this.createThreemaId("id"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - this.privateKeyField = this.createPrivateKeyField("privateKey"); - this.messageIdField = this.createTextField("messageId"); - this.nonceField = this.createByteArrayField("nonce"); - this.outputFolderField = this.createFolderField("outputFolder", false); - } - - @Override - protected void execute() throws Exception { - String id = this.threemaId.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); - byte[] privateKey = this.privateKeyField.getValue(); - byte[] nonce = this.nonceField.getValue(); - String messageId = this.messageIdField.getValue(); - Path outputFolder = this.outputFolderField.getValue(); - - E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); - - byte[] box = DataUtils.hexStringToByteArray(this.readStream(System.in, "UTF-8").trim()); - - E2EHelper.ReceiveMessageResult res = e2EHelper.receiveMessage(id, messageId, box, nonce, outputFolder); - System.out.println(res.toString()); - System.out.println(res.getFiles().toString()); - } -} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/DecryptCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/DecryptCommand.java deleted file mode 100644 index fd74ad1..0000000 --- a/source/src/main/java/ch/threema/apitool/console/commands/DecryptCommand.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.console.commands; - -import ch.threema.apitool.console.commands.fields.ByteArrayField; -import ch.threema.apitool.console.commands.fields.PrivateKeyField; -import ch.threema.apitool.console.commands.fields.PublicKeyField; -import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; -import ch.threema.apitool.messages.ThreemaMessage; - -public class DecryptCommand extends Command { - private final PrivateKeyField privateKeyField; - private final PublicKeyField publicKeyField; - private final ByteArrayField nonceField; - - public DecryptCommand() { - super("Decrypt", - "Decrypt standard input using the given recipient private key and sender public key. The nonce must be given on the command line, and the box (hex) on standard input. Prints the decrypted message to standard output."); - - this.privateKeyField = this.createPrivateKeyField("privateKey"); - this.publicKeyField = this.createPublicKeyField("publicKey"); - this.nonceField = this.createByteArrayField("nonce"); - } - - @Override - protected void execute() throws Exception { - byte[] privateKey = this.privateKeyField.getValue(); - byte[] publicKey = this.publicKeyField.getValue(); - byte[] nonce = this.nonceField.getValue(); - - /* read box from stdin */ - byte[] box = DataUtils.hexStringToByteArray(readStream(System.in, "UTF-8")); - - ThreemaMessage message = CryptTool.decryptMessage(box, privateKey, publicKey, nonce); - - System.out.println(message); - } -} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/EncryptCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/EncryptCommand.java deleted file mode 100644 index 8abaf04..0000000 --- a/source/src/main/java/ch/threema/apitool/console/commands/EncryptCommand.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.console.commands; - -import ch.threema.apitool.console.commands.fields.PrivateKeyField; -import ch.threema.apitool.console.commands.fields.PublicKeyField; -import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; -import ch.threema.apitool.results.EncryptResult; - -public class EncryptCommand extends Command { - private final PrivateKeyField privateKeyField; - private final PublicKeyField publicKeyField; - - public EncryptCommand() { - super("Encrypt", - "Encrypt standard input using the given sender private key and recipient public key. Prints two lines to standard output: first the nonce (hex), and then the box (hex)."); - - this.privateKeyField = this.createPrivateKeyField("privateKey"); - this.publicKeyField = this.createPublicKeyField("publicKey"); - } - - @Override - protected void execute() throws Exception { - byte[] privateKey = this.privateKeyField.getValue(); - byte[] publicKey = this.publicKeyField.getValue(); - - /* read text from stdin */ - String text = readStream(System.in, "UTF-8").trim(); - - EncryptResult res = CryptTool.encryptTextMessage(text, privateKey, publicKey); - - System.out.println(DataUtils.byteArrayToHexString(res.getNonce())); - System.out.println(DataUtils.byteArrayToHexString(res.getResult())); - } -} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/SendE2EFileMessageCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/SendE2EFileMessageCommand.java deleted file mode 100644 index 3029618..0000000 --- a/source/src/main/java/ch/threema/apitool/console/commands/SendE2EFileMessageCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.console.commands; - -import ch.threema.apitool.console.commands.fields.FileField; -import ch.threema.apitool.console.commands.fields.PrivateKeyField; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; -import ch.threema.apitool.helpers.E2EHelper; - -import java.io.File; - -public class SendE2EFileMessageCommand extends Command { - private final ThreemaIDField threemaId; - private final ThreemaIDField fromField; - private final TextField secretField; - private final PrivateKeyField privateKeyField; - private final FileField fileField; - private final FileField thumbnailField; - - public SendE2EFileMessageCommand() { - super("Send End-to-End Encrypted File Message", - "Encrypt the file (and thumbnail) and send a file message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success." - ); - this.threemaId = this.createThreemaId("to"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - this.privateKeyField = this.createPrivateKeyField("privateKey"); - this.fileField = this.createFileField("file"); - this.thumbnailField = this.createFileField("thumbnail", false); - - } - - @Override - protected void execute() throws Exception { - String to = this.threemaId.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); - byte[] privateKey = this.privateKeyField.getValue(); - File file = this.fileField.getValue(); - File thumbnail = this.thumbnailField.getValue(); - - E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); - String messageId = e2EHelper.sendFileMessage(to, file, thumbnail); - System.out.println("MessageId: " + messageId); - } -} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/SendE2EImageMessageCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/SendE2EImageMessageCommand.java deleted file mode 100644 index 14a248f..0000000 --- a/source/src/main/java/ch/threema/apitool/console/commands/SendE2EImageMessageCommand.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.console.commands; - -import ch.threema.apitool.console.commands.fields.FolderField; -import ch.threema.apitool.console.commands.fields.PrivateKeyField; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; -import ch.threema.apitool.helpers.E2EHelper; - -import java.nio.file.Path; - -public class SendE2EImageMessageCommand extends Command { - private final ThreemaIDField toField; - private final ThreemaIDField fromField; - private final TextField secretField; - private final PrivateKeyField privateKeyField; - private final FolderField imageFilePath; - - public SendE2EImageMessageCommand() { - super("Send End-to-End Encrypted Image Message", - "Encrypt standard input and send the message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success."); - - this.toField = this.createThreemaId("to", true); - this.fromField = this.createThreemaId("from", true); - this.secretField = this.createTextField("secret", true); - this.privateKeyField = this.createPrivateKeyField("privateKey", true); - this.imageFilePath = this.createFolderField("imageFilePath", true); - } - - @Override - protected void execute() throws Exception { - String to = this.toField.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); - byte[] privateKey = this.privateKeyField.getValue(); - Path imageFilePath = this.imageFilePath.getValue(); - - E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); - String messageId = e2EHelper.sendImageMessage(to, imageFilePath.toString()); - System.out.println("MessageId: " + messageId); - } -} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/SendE2ETextMessageCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/SendE2ETextMessageCommand.java deleted file mode 100644 index 1aaf576..0000000 --- a/source/src/main/java/ch/threema/apitool/console/commands/SendE2ETextMessageCommand.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.console.commands; - -import ch.threema.apitool.console.commands.fields.PrivateKeyField; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; -import ch.threema.apitool.helpers.E2EHelper; - -public class SendE2ETextMessageCommand extends Command { - private final ThreemaIDField threemaId; - private final ThreemaIDField fromField; - private final TextField secretField; - private final PrivateKeyField privateKeyField; - - public SendE2ETextMessageCommand() { - super("Send End-to-End Encrypted Text Message", - "Encrypt standard input and send the message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success."); - this.threemaId = this.createThreemaId("to"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - this.privateKeyField = this.createPrivateKeyField("privateKey"); - } - - @Override - protected void execute() throws Exception { - String to = this.threemaId.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); - byte[] privateKey = this.privateKeyField.getValue(); - - String text = this.readStream(System.in, "UTF-8").trim(); - - E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); - String messageId = e2EHelper.sendTextMessage(to, text); - System.out.println("MessageId: " + messageId); - } -} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/SendSimpleMessageCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/SendSimpleMessageCommand.java deleted file mode 100644 index 1fbef43..0000000 --- a/source/src/main/java/ch/threema/apitool/console/commands/SendSimpleMessageCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.console.commands; - -import ch.threema.apitool.APIConnector; -import ch.threema.apitool.console.commands.fields.TextField; -import ch.threema.apitool.console.commands.fields.ThreemaIDField; - -public class SendSimpleMessageCommand extends Command { - private final ThreemaIDField threemaId; - private final ThreemaIDField fromField; - private final TextField secretField; - - public SendSimpleMessageCommand() { - super("Send Simple Message", - "Send a message from standard input with server-side encryption to the given ID. 'from' is the API identity and 'secret' is the API secret. Returns the message ID on success."); - - this.threemaId = this.createThreemaId("to"); - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - } - - @Override - protected void execute() throws Exception { - String to = this.threemaId.getValue(); - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); - - String text = readStream(System.in, "UTF-8").trim(); - - APIConnector apiConnector = this.createConnector(from, secret); - String messageId = apiConnector.sendTextMessageSimple(to, text); - System.out.println(messageId); - } -} diff --git a/source/src/main/java/ch/threema/apitool/helpers/E2EHelper.java b/source/src/main/java/ch/threema/apitool/helpers/E2EHelper.java deleted file mode 100644 index c8a2d7f..0000000 --- a/source/src/main/java/ch/threema/apitool/helpers/E2EHelper.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.helpers; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.io.IOUtils; - -import com.neilalexander.jnacl.NaCl; - -import ch.threema.apitool.APIConnector; -import ch.threema.apitool.CryptTool; -import ch.threema.apitool.exceptions.InvalidKeyException; -import ch.threema.apitool.exceptions.MessageParseException; -import ch.threema.apitool.exceptions.NotAllowedException; -import ch.threema.apitool.messages.FileMessage; -import ch.threema.apitool.messages.ImageMessage; -import ch.threema.apitool.messages.ThreemaMessage; -import ch.threema.apitool.results.CapabilityResult; -import ch.threema.apitool.results.EncryptResult; -import ch.threema.apitool.results.UploadResult; - -/** - * Helper to handle Threema end-to-end encryption. - */ -public class E2EHelper { - private final APIConnector apiConnector; - private final byte[] privateKey; - - public class ReceiveMessageResult { - private final String messageId; - private final ThreemaMessage message; - protected List files = new ArrayList<>(); - protected List errors = new ArrayList<>(); - - public ReceiveMessageResult(String messageId, ThreemaMessage message) { - this.messageId = messageId; - this.message = message; - } - - public List getFiles() { - return this.files; - } - - public List getErrors() { - return this.errors; - } - - public String getMessageId() { - return messageId; - } - - public ThreemaMessage getMessage() { - return message; - } - } - - public E2EHelper(APIConnector apiConnector, byte[] privateKey) { - this.apiConnector = apiConnector; - this.privateKey = privateKey; - } - - /** - * Encrypt a text message and send it to the given recipient. - * - * @param threemaId target Threema ID - * @param text the text to send - * @return generated message ID - */ - public String sendTextMessage(String threemaId, String text) throws Exception { - // fetch public key - byte[] publicKey = this.apiConnector.lookupKey(threemaId); - - if (publicKey == null) { - throw new Exception("invalid threema id"); - } - EncryptResult res = CryptTool.encryptTextMessage(text, this.privateKey, publicKey); - - return this.apiConnector.sendE2EMessage(threemaId, res.getNonce(), res.getResult()); - } - - /** - * Encrypt an image message and send it to the given recipient. - * - * @param threemaId target Threema ID - * @param imageFilePath path to read image data from - * @return generated message ID - * @throws NotAllowedException - * @throws IOException - * @throws InvalidKeyException - */ - public String sendImageMessage(String threemaId, String imageFilePath) - throws NotAllowedException, IOException, InvalidKeyException { - // fetch public key - byte[] publicKey = this.apiConnector.lookupKey(threemaId); - - if (publicKey == null) { - throw new InvalidKeyException("invalid threema id"); - } - - // check capability of a key - CapabilityResult capabilityResult = this.apiConnector.lookupKeyCapability(threemaId); - if (capabilityResult == null || !capabilityResult.canImage()) { - throw new NotAllowedException(); - } - - byte[] fileData = Files.readAllBytes(Paths.get(imageFilePath)); - if (fileData == null) { - throw new IOException("invalid file"); - } - - // encrypt the image - EncryptResult encryptResult = CryptTool.encrypt(fileData, this.privateKey, publicKey); - - // upload the image - UploadResult uploadResult = apiConnector.uploadFile(encryptResult); - - if (!uploadResult.isSuccess()) { - throw new IOException("could not upload file (upload response " + uploadResult.getResponseCode() + ")"); - } - - // send it - EncryptResult imageMessage = CryptTool.encryptImageMessage(encryptResult, uploadResult, privateKey, publicKey); - - return apiConnector.sendE2EMessage(threemaId, imageMessage.getNonce(), imageMessage.getResult()); - } - - /** - * Encrypt a file message and send it to the given recipient. - * The thumbnailMessagePath can be null. - * - * @param threemaId target Threema ID - * @param fileMessageFile the file to be sent - * @param thumbnailMessagePath file for thumbnail; if not set, no thumbnail will be sent - * @return generated message ID - * @throws InvalidKeyException - * @throws IOException - * @throws NotAllowedException - */ - public String sendFileMessage(String threemaId, File fileMessageFile, File thumbnailMessagePath) - throws InvalidKeyException, IOException, NotAllowedException { - // fetch public key - byte[] publicKey = this.apiConnector.lookupKey(threemaId); - - if (publicKey == null) { - throw new InvalidKeyException("invalid threema id"); - } - - // check capability of a key - CapabilityResult capabilityResult = this.apiConnector.lookupKeyCapability(threemaId); - if (capabilityResult == null || !capabilityResult.canImage()) { - throw new NotAllowedException(); - } - - if (!fileMessageFile.isFile()) { - throw new IOException("invalid file"); - } - - byte[] fileData = this.readFile(fileMessageFile); - - if (fileData == null) { - throw new IOException("invalid file"); - } - - // encrypt the image - EncryptResult encryptResult = CryptTool.encryptFileData(fileData); - - // upload the image - UploadResult uploadResult = apiConnector.uploadFile(encryptResult); - - if (!uploadResult.isSuccess()) { - throw new IOException("could not upload file (upload response " + uploadResult.getResponseCode() + ")"); - } - - UploadResult uploadResultThumbnail = null; - - if (thumbnailMessagePath != null && thumbnailMessagePath.isFile()) { - byte[] thumbnailData = this.readFile(thumbnailMessagePath); - if (thumbnailData == null) { - throw new IOException("invalid thumbnail file"); - } - - // encrypt the thumbnail - EncryptResult encryptResultThumbnail = CryptTool.encryptFileThumbnailData(fileData, - encryptResult.getSecret()); - - // upload the thumbnail - uploadResultThumbnail = this.apiConnector.uploadFile(encryptResultThumbnail); - } - - // send it - EncryptResult fileMessage = CryptTool.encryptFileMessage(encryptResult, uploadResult, - Files.probeContentType(fileMessageFile.toPath()), fileMessageFile.getName(), - (int) fileMessageFile.length(), uploadResultThumbnail, privateKey, publicKey); - - return this.apiConnector.sendE2EMessage(threemaId, fileMessage.getNonce(), fileMessage.getResult()); - } - - /** - * Decrypt a Message and download the blobs of the Message (e.g. image or file) - * - * @param threemaId Threema ID of the sender - * @param messageId Message ID - * @param box Encrypted box data of the file/image message - * @param nonce Nonce that was used for message encryption - * @param outputFolder Output folder for storing decrypted images/files - * @return result of message reception - * @throws IOException - * @throws InvalidKeyException - * @throws MessageParseException - */ - public ReceiveMessageResult receiveMessage(String threemaId, String messageId, byte[] box, byte[] nonce, - Path outputFolder) throws IOException, InvalidKeyException, MessageParseException { - // fetch public key - byte[] publicKey = this.apiConnector.lookupKey(threemaId); - - if (publicKey == null) { - throw new InvalidKeyException("invalid threema id"); - } - - ThreemaMessage message = CryptTool.decryptMessage(box, this.privateKey, publicKey, nonce); - if (message == null) { - return null; - } - - ReceiveMessageResult result = new ReceiveMessageResult(messageId, message); - - if (message instanceof ImageMessage) { - // download image - ImageMessage imageMessage = (ImageMessage) message; - byte[] fileData = this.apiConnector.downloadFile(imageMessage.getBlobId()); - - if (fileData == null) { - throw new MessageParseException(); - } - - byte[] decryptedFileContent = CryptTool.decrypt(fileData, privateKey, publicKey, imageMessage.getNonce()); - File imageFile = new File(outputFolder.toString() + "/" + messageId + ".jpg"); - FileOutputStream fos = new FileOutputStream(imageFile); - fos.write(decryptedFileContent); - fos.close(); - - result.files.add(imageFile); - } else if (message instanceof FileMessage) { - // download file - FileMessage fileMessage = (FileMessage) message; - byte[] fileData = this.apiConnector.downloadFile(fileMessage.getBlobId()); - - byte[] decryptedFileData = CryptTool.decryptFileData(fileData, fileMessage.getEncryptionKey()); - File file = new File(outputFolder.toString() + "/" + messageId + "-" + fileMessage.getFileName()); - FileOutputStream fos = new FileOutputStream(file); - fos.write(decryptedFileData); - fos.close(); - - result.files.add(file); - - if (fileMessage.getThumbnailBlobId() != null) { - byte[] thumbnailData = this.apiConnector.downloadFile(fileMessage.getThumbnailBlobId()); - - byte[] decryptedThumbnailData = CryptTool.decryptFileThumbnailData(thumbnailData, - fileMessage.getEncryptionKey()); - File thumbnailFile = new File(outputFolder.toString() + "/" + messageId + "-thumbnail.jpg"); - fos = new FileOutputStream(thumbnailFile); - fos.write(decryptedThumbnailData); - fos.close(); - - result.files.add(thumbnailFile); - } - } - - return result; - } - - /** - * Read file data from file - store at offset in byte array for in-place encryption - * - * @param file input file - * @return file data with padding/offset for NaCl - * @throws IOException - */ - private byte[] readFile(File file) throws IOException { - int fileLength = (int) file.length(); - byte[] fileData = new byte[fileLength + NaCl.BOXOVERHEAD]; - IOUtils.readFully(new FileInputStream(file), fileData, NaCl.BOXOVERHEAD, fileLength); - return fileData; - } -} diff --git a/source/src/main/java/ch/threema/apitool/messages/DeliveryReceipt.java b/source/src/main/java/ch/threema/apitool/messages/DeliveryReceipt.java deleted file mode 100644 index 6f79f76..0000000 --- a/source/src/main/java/ch/threema/apitool/messages/DeliveryReceipt.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.messages; - -import ch.threema.apitool.MessageId; - -import java.util.List; - -/** - * A delivery receipt message that can be sent/received with end-to-end encryption via Threema. - * Each delivery receipt message confirms the receipt of one or multiple regular messages. - */ -public class DeliveryReceipt extends ThreemaMessage { - - public static final int TYPE_CODE = 0x80; - - private final Type receiptType; - private final List ackedMessageIds; - - public DeliveryReceipt(Type receiptType, List ackedMessageIds) { - this.receiptType = receiptType; - this.ackedMessageIds = ackedMessageIds; - } - - public Type getReceiptType() { - return receiptType; - } - - public List getAckedMessageIds() { - return ackedMessageIds; - } - - @Override - public int getTypeCode() { - return TYPE_CODE; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Delivery receipt ("); - sb.append(receiptType); - sb.append("): "); - int i = 0; - for (MessageId messageId : ackedMessageIds) { - if (i != 0) - sb.append(", "); - sb.append(messageId); - i++; - } - return sb.toString(); - } - - /** - * A delivery receipt type. The following types are defined: - * - *
    - *
  • RECEIVED: the message has been received and decrypted on the recipient's device
  • - *
  • READ: the message has been shown to the user in the chat view - * (note that this status can be disabled)
  • - *
  • USER_ACK: the user has explicitly acknowledged the message (usually by - * long-pressing it and choosing the "acknowledge" option)
  • - *
- */ - public enum Type { - RECEIVED(1), READ(2), USER_ACK(3); - - private final int code; - - Type(int code) { - this.code = code; - } - - public int getCode() { - return code; - } - - public static Type get(int code) { - for (Type t : values()) { - if (t.code == code) - return t; - } - return null; - } - } - - @Override - public byte[] getData() { - //Not implemented yet - return new byte[0]; - } -} diff --git a/source/src/main/java/ch/threema/apitool/messages/FileMessage.java b/source/src/main/java/ch/threema/apitool/messages/FileMessage.java deleted file mode 100644 index f630c08..0000000 --- a/source/src/main/java/ch/threema/apitool/messages/FileMessage.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.messages; - -import java.io.UnsupportedEncodingException; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; - -import ch.threema.apitool.DataUtils; -import ch.threema.apitool.exceptions.BadMessageException; - -/** - * A file message that can be sent/received with end-to-end encryption via Threema. - */ -public class FileMessage extends ThreemaMessage { - private final static String KEY_BLOB_ID = "b"; - private final static String KEY_THUMBNAIL_BLOB_ID = "t"; - private final static String KEY_ENCRYPTION_KEY = "k"; - private final static String KEY_MIME_TYPE = "m"; - private final static String KEY_FILE_NAME = "n"; - private final static String KEY_FILE_SIZE = "s"; - private final static String KEY_TYPE = "i"; - - public static final int TYPE_CODE = 0x17; - - private final byte[] blobId; - private final byte[] encryptionKey; - private final String mimeType; - private final String fileName; - private final int fileSize; - private final byte[] thumbnailBlobId; - - public FileMessage(byte[] blobId, byte[] encryptionKey, String mimeType, String fileName, int fileSize, - byte[] thumbnailBlobId) { - this.blobId = blobId; - this.encryptionKey = encryptionKey; - this.mimeType = mimeType; - this.fileName = fileName; - this.fileSize = fileSize; - this.thumbnailBlobId = thumbnailBlobId; - } - - public byte[] getBlobId() { - return this.blobId; - } - - public byte[] getEncryptionKey() { - return this.encryptionKey; - } - - public String getMimeType() { - return this.mimeType; - } - - public String getFileName() { - return this.fileName; - } - - public int getFileSize() { - return this.fileSize; - } - - public byte[] getThumbnailBlobId() { - return this.thumbnailBlobId; - } - - @Override - public int getTypeCode() { - return TYPE_CODE; - } - - @Override - public String toString() { - return "file message " + this.fileName; - } - - @Override - public byte[] getData() throws BadMessageException { - JsonObject o = new JsonObject(); - try { - o.addProperty(KEY_BLOB_ID, DataUtils.byteArrayToHexString(this.blobId)); - o.addProperty(KEY_THUMBNAIL_BLOB_ID, - this.thumbnailBlobId != null ? DataUtils.byteArrayToHexString(this.thumbnailBlobId) : null); - o.addProperty(KEY_ENCRYPTION_KEY, DataUtils.byteArrayToHexString(this.encryptionKey)); - o.addProperty(KEY_MIME_TYPE, this.mimeType); - o.addProperty(KEY_FILE_NAME, this.fileName); - o.addProperty(KEY_FILE_SIZE, this.fileSize); - o.addProperty(KEY_TYPE, 0); - } catch (Exception e) { - throw new BadMessageException(); - } - - try { - return o.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - return null; - } - } - - public static FileMessage fromString(String json) throws BadMessageException { - try { - JsonObject o = new Gson().fromJson(json, JsonObject.class); - byte[] encryptionKey = DataUtils.hexStringToByteArray(o.get(KEY_ENCRYPTION_KEY).getAsString()); - String mimeType = o.get(KEY_MIME_TYPE).getAsString(); - int fileSize = o.get(KEY_FILE_SIZE).getAsInt(); - byte[] blobId = DataUtils.hexStringToByteArray(o.get(KEY_BLOB_ID).getAsString()); - - String fileName; - byte[] thumbnailBlobId = null; - - // optional field - if (o.has(KEY_THUMBNAIL_BLOB_ID)) { - thumbnailBlobId = DataUtils.hexStringToByteArray(o.get(KEY_THUMBNAIL_BLOB_ID).getAsString()); - } - - if (o.has(KEY_FILE_NAME)) { - fileName = o.get(KEY_FILE_NAME).getAsString(); - } else { - fileName = "unnamed"; - } - - return new FileMessage(blobId, encryptionKey, mimeType, fileName, fileSize, thumbnailBlobId); - } catch (JsonSyntaxException e) { - throw new BadMessageException(); - } - } -} diff --git a/source/src/main/java/ch/threema/apitool/results/CapabilityResult.java b/source/src/main/java/ch/threema/apitool/results/CapabilityResult.java deleted file mode 100644 index 65284c9..0000000 --- a/source/src/main/java/ch/threema/apitool/results/CapabilityResult.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.results; - -/** - * Result of a capability lookup - */ -public class CapabilityResult { - private final String key; - private final String[] capabilities; - - public CapabilityResult(String key, String[] capabilities) { - this.key = key; - this.capabilities = capabilities; - } - - /** - * Get all capabilities as a string array. - */ - public String[] getCapabilities() { - return capabilities; - } - - /** - * Check whether the Threema ID can receive text - */ - public boolean canText() { - return this.can("text"); - } - - /** - * Check whether the Threema ID can receive images - */ - public boolean canImage() { - return this.can("image"); - } - - /** - * Check whether the Threema ID can receive videos - */ - public boolean canVideo() { - return this.can("video"); - } - - /** - * Check whether the Threema ID can receive audio - */ - public boolean canAudio() { - return this.can("audio"); - } - - /** - * Check whether the Threema ID can receive files - */ - public boolean canFile() { - return this.can("file"); - } - - private boolean can(String key) { - for(String k: this.capabilities) { - if(k.equals(key)) { - return true; - } - } - return false; - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append(this.key).append(": "); - for(int n = 0; n < this.capabilities.length; n++) { - if(n > 0) { - b.append(","); - } - b.append(this.capabilities[n]); - } - return b.toString(); - } - - public String getKey() { - return key; - } -} diff --git a/source/src/main/java/ch/threema/apitool/results/EncryptResult.java b/source/src/main/java/ch/threema/apitool/results/EncryptResult.java deleted file mode 100644 index 71fb91a..0000000 --- a/source/src/main/java/ch/threema/apitool/results/EncryptResult.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.results; - -/** - * Result of a data encryption - */ -public class EncryptResult { - private final byte[] result; - private final byte[] secret; - private final byte[] nonce; - - public EncryptResult(byte[] result, byte[] secret, byte[] nonce) { - this.result = result; - this.secret = secret; - this.nonce = nonce; - } - - /** - * @return the encrypted data - */ - public byte[] getResult() { - return this.result; - } - - /** - * @return the size (in bytes) of the encrypted data - */ - public int getSize() { - return this.result.length; - } - - /** - * @return the nonce that was used for encryption - */ - public byte[] getNonce() { - return this.nonce; - } - - /** - * @return the secret that was used for encryption (only for symmetric encryption, e.g. files) - */ - public byte[] getSecret() { - return secret; - } -} diff --git a/source/src/main/java/ch/threema/apitool/results/UploadResult.java b/source/src/main/java/ch/threema/apitool/results/UploadResult.java deleted file mode 100644 index 055b9ad..0000000 --- a/source/src/main/java/ch/threema/apitool/results/UploadResult.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool.results; - -/** - * Result of a file upload - */ -public class UploadResult { - private final int responseCode; - private final byte[] blobId; - - public UploadResult(int responseCode, byte[] blobId) { - this.responseCode = responseCode; - this.blobId = blobId; - } - - /** - * @return the blob ID that has been created - */ - public byte[] getBlobId() { - return this.blobId; - } - - /** - * @return whether the upload succeeded - */ - public boolean isSuccess() { - return this.responseCode == 200; - } - - /** - * @return the response code of the upload - */ - public int getResponseCode() { - return this.responseCode; - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/NaCl.java b/source/src/main/java/com/neilalexander/jnacl/NaCl.java deleted file mode 100644 index 5cddd5d..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/NaCl.java +++ /dev/null @@ -1,330 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl; - -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Formatter; - -import com.neilalexander.jnacl.crypto.curve25519xsalsa20poly1305; -import com.neilalexander.jnacl.crypto.xsalsa20; -import com.neilalexander.jnacl.crypto.xsalsa20poly1305; - -public class NaCl { - public static final int PUBLICKEYBYTES = 32; - public static final int SECRETKEYBYTES = 32; - public static final int BEFORENMBYTES = 32; - public static final int NONCEBYTES = 24; - public static final int ZEROBYTES = 32; - public static final int BOXZEROBYTES = 16; - public static final int BOXOVERHEAD = ZEROBYTES - BOXZEROBYTES; - public static final int SYMMKEYBYTES = 32; - public static final int STREAMKEYBYTES = 32; - - private final byte[] precomputed = new byte[BEFORENMBYTES]; - - /* Perform self test before anything else */ - static { - selfTest(); - } - - public NaCl(byte[] privatekey, byte[] publickey) { - if (privatekey.length != SECRETKEYBYTES) - throw new Error("Invalid private key length"); - - if (publickey.length != PUBLICKEYBYTES) - throw new Error("Invalid public key length"); - - curve25519xsalsa20poly1305.crypto_box_beforenm(this.precomputed, publickey, privatekey); - } - - public NaCl(String privatekey, String publickey) { - this(getBinary(privatekey), getBinary(publickey)); - } - - public byte[] encrypt(byte[] input, byte[] nonce) { - return encrypt(input, input.length, nonce); - } - - public byte[] encrypt(byte[] input, int inputlength, byte[] nonce) { - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - byte[] output = new byte[inputlength + BOXOVERHEAD]; - curve25519xsalsa20poly1305.crypto_box_afternm_nopad(output, 0, input, 0, input.length, nonce, this.precomputed); - - return output; - } - - public byte[] decrypt(byte[] input, byte[] nonce) { - return decrypt(input, input.length, nonce); - } - - public byte[] decrypt(byte[] input, int inputlength, byte[] nonce) { - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - if (inputlength < BOXOVERHEAD) - return null; - - byte[] output = new byte[inputlength - BOXOVERHEAD]; - if (curve25519xsalsa20poly1305.crypto_box_open_afternm_nopad(output, 0, input, 0, input.length, nonce, - this.precomputed) != 0) - return null; - - return output; - } - - public static void genkeypair(byte[] publickey, byte[] privatekey) { - genkeypair(publickey, privatekey, null); - } - - public static void genkeypair(byte[] publickey, byte[] privatekey, byte[] seed) { - SecureRandom random = new SecureRandom(); - - random.nextBytes(privatekey); - - if (seed != null) { - if (seed.length != SECRETKEYBYTES) - throw new Error("Invalid seed length"); - - for (int i = 0; i < SECRETKEYBYTES; i++) - privatekey[i] ^= seed[i]; - } - - curve25519xsalsa20poly1305.crypto_box_getpublickey(publickey, privatekey); - } - - public static byte[] derivePublicKey(byte[] privatekey) { - if (privatekey.length != SECRETKEYBYTES) - throw new Error("Invalid private key length"); - - byte[] publickey = new byte[PUBLICKEYBYTES]; - curve25519xsalsa20poly1305.crypto_box_getpublickey(publickey, privatekey); - return publickey; - } - - public static byte[] symmetricEncryptData(byte[] input, byte[] key, byte[] nonce) { - if (key.length != SYMMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - byte[] output = new byte[input.length + BOXOVERHEAD]; - xsalsa20poly1305.crypto_secretbox_nopad(output, 0, input, 0, input.length, nonce, key); - - return output; - } - - /** - * In-place version of {@link #symmetricEncryptData(byte[], byte[], byte[])} that stores the output - * in the same byte array as the input. The input data must begin at offset {@link #BOXOVERHEAD} in - * the array (the first BOXOVERHEAD bytes are ignored and will be overwritten with the message - * authentication code during encryption). - * - * @param io plaintext on input (starting at offset BOXOVERHEAD), ciphertext on return (full array) - * @param key encryption key - * @param nonce encryption nonce - */ - public static void symmetricEncryptDataInplace(byte[] io, byte[] key, byte[] nonce) { - - if (key.length != SYMMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - if (io.length < BOXOVERHEAD) - throw new Error("Invalid I/O length"); - - xsalsa20poly1305.crypto_secretbox_nopad(io, 0, io, BOXOVERHEAD, io.length - BOXOVERHEAD, nonce, key); - } - - public static byte[] symmetricDecryptData(byte[] input, byte[] key, byte[] nonce) { - if (key.length != SYMMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - byte[] output = new byte[input.length - BOXOVERHEAD]; - if (xsalsa20poly1305.crypto_secretbox_open_nopad(output, 0, input, 0, input.length, nonce, key) != 0) - return null; - - return output; - } - - /** - * In-place version of {@link #symmetricDecryptData(byte[], byte[], byte[])} that stores the output in - * the same byte array as the input. Note that the decrypted output is shorter than the input, so the - * last {@link #BOXOVERHEAD} bytes should be ignored in the decrypted output. - * - * @param io ciphertext on input (full array), plaintext on output (last BOXOVERHEAD bytes set to zero) - * @param key encryption key - * @param nonce encryption nonce - * @return decryption successful true/false - */ - public static boolean symmetricDecryptDataInplace(byte[] io, byte[] key, byte[] nonce) { - if (key.length != SYMMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - if (io.length < BOXOVERHEAD) - throw new Error("Invalid I/O length"); - - if (xsalsa20poly1305.crypto_secretbox_open_nopad(io, 0, io, 0, io.length, nonce, key) != 0) - return false; - - /* zeroize last bytes */ - for (int i = io.length - BOXOVERHEAD; i < io.length; i++) - io[i] = 0; - - return true; - } - - public static byte[] streamCryptData(byte[] input, byte[] key, byte[] nonce) { - if (key.length != STREAMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - byte[] output = new byte[input.length]; - xsalsa20.crypto_stream_xor(output, input, input.length, nonce, key); - - return output; - } - - public static byte[] getBinary(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - - for (int i = 0; i < len; i += 2) - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); - - return data; - } - - public static String asHex(byte[] buf) { - try (Formatter formatter = new Formatter()) { - for (byte b : buf) - formatter.format("%02x", b); - return formatter.toString(); - } - } - - public static String asHex(int[] buf) { - try (Formatter formatter = new Formatter()) { - for (int b : buf) - formatter.format("%02x", b); - return formatter.toString(); - } - } - - public static void selfTest() { - /* test vectors from tests/box.* in nacl distribution */ - byte alicepk[] = new byte[] { (byte) 0x85, (byte) 0x20, (byte) 0xf0, (byte) 0x09, (byte) 0x89, (byte) 0x30, - (byte) 0xa7, (byte) 0x54, (byte) 0x74, (byte) 0x8b, (byte) 0x7d, (byte) 0xdc, (byte) 0xb4, (byte) 0x3e, - (byte) 0xf7, (byte) 0x5a, (byte) 0x0d, (byte) 0xbf, (byte) 0x3a, (byte) 0x0d, (byte) 0x26, (byte) 0x38, - (byte) 0x1a, (byte) 0xf4, (byte) 0xeb, (byte) 0xa4, (byte) 0xa9, (byte) 0x8e, (byte) 0xaa, (byte) 0x9b, - (byte) 0x4e, (byte) 0x6a }; - - byte alicesk[] = { (byte) 0x77, (byte) 0x07, (byte) 0x6d, (byte) 0x0a, (byte) 0x73, (byte) 0x18, (byte) 0xa5, - (byte) 0x7d, (byte) 0x3c, (byte) 0x16, (byte) 0xc1, (byte) 0x72, (byte) 0x51, (byte) 0xb2, (byte) 0x66, - (byte) 0x45, (byte) 0xdf, (byte) 0x4c, (byte) 0x2f, (byte) 0x87, (byte) 0xeb, (byte) 0xc0, (byte) 0x99, - (byte) 0x2a, (byte) 0xb1, (byte) 0x77, (byte) 0xfb, (byte) 0xa5, (byte) 0x1d, (byte) 0xb9, (byte) 0x2c, - (byte) 0x2a }; - - byte bobpk[] = { (byte) 0xde, (byte) 0x9e, (byte) 0xdb, (byte) 0x7d, (byte) 0x7b, (byte) 0x7d, (byte) 0xc1, - (byte) 0xb4, (byte) 0xd3, (byte) 0x5b, (byte) 0x61, (byte) 0xc2, (byte) 0xec, (byte) 0xe4, (byte) 0x35, - (byte) 0x37, (byte) 0x3f, (byte) 0x83, (byte) 0x43, (byte) 0xc8, (byte) 0x5b, (byte) 0x78, (byte) 0x67, - (byte) 0x4d, (byte) 0xad, (byte) 0xfc, (byte) 0x7e, (byte) 0x14, (byte) 0x6f, (byte) 0x88, (byte) 0x2b, - (byte) 0x4f }; - - byte bobsk[] = { (byte) 0x5d, (byte) 0xab, (byte) 0x08, (byte) 0x7e, (byte) 0x62, (byte) 0x4a, (byte) 0x8a, - (byte) 0x4b, (byte) 0x79, (byte) 0xe1, (byte) 0x7f, (byte) 0x8b, (byte) 0x83, (byte) 0x80, (byte) 0x0e, - (byte) 0xe6, (byte) 0x6f, (byte) 0x3b, (byte) 0xb1, (byte) 0x29, (byte) 0x26, (byte) 0x18, (byte) 0xb6, - (byte) 0xfd, (byte) 0x1c, (byte) 0x2f, (byte) 0x8b, (byte) 0x27, (byte) 0xff, (byte) 0x88, (byte) 0xe0, - (byte) 0xeb }; - - byte nonce[] = { (byte) 0x69, (byte) 0x69, (byte) 0x6e, (byte) 0xe9, (byte) 0x55, (byte) 0xb6, (byte) 0x2b, - (byte) 0x73, (byte) 0xcd, (byte) 0x62, (byte) 0xbd, (byte) 0xa8, (byte) 0x75, (byte) 0xfc, (byte) 0x73, - (byte) 0xd6, (byte) 0x82, (byte) 0x19, (byte) 0xe0, (byte) 0x03, (byte) 0x6b, (byte) 0x7a, (byte) 0x0b, - (byte) 0x37 }; - - byte m[] = { (byte) 0xbe, (byte) 0x07, (byte) 0x5f, (byte) 0xc5, (byte) 0x3c, (byte) 0x81, (byte) 0xf2, - (byte) 0xd5, (byte) 0xcf, (byte) 0x14, (byte) 0x13, (byte) 0x16, (byte) 0xeb, (byte) 0xeb, (byte) 0x0c, - (byte) 0x7b, (byte) 0x52, (byte) 0x28, (byte) 0xc5, (byte) 0x2a, (byte) 0x4c, (byte) 0x62, (byte) 0xcb, - (byte) 0xd4, (byte) 0x4b, (byte) 0x66, (byte) 0x84, (byte) 0x9b, (byte) 0x64, (byte) 0x24, (byte) 0x4f, - (byte) 0xfc, (byte) 0xe5, (byte) 0xec, (byte) 0xba, (byte) 0xaf, (byte) 0x33, (byte) 0xbd, (byte) 0x75, - (byte) 0x1a, (byte) 0x1a, (byte) 0xc7, (byte) 0x28, (byte) 0xd4, (byte) 0x5e, (byte) 0x6c, (byte) 0x61, - (byte) 0x29, (byte) 0x6c, (byte) 0xdc, (byte) 0x3c, (byte) 0x01, (byte) 0x23, (byte) 0x35, (byte) 0x61, - (byte) 0xf4, (byte) 0x1d, (byte) 0xb6, (byte) 0x6c, (byte) 0xce, (byte) 0x31, (byte) 0x4a, (byte) 0xdb, - (byte) 0x31, (byte) 0x0e, (byte) 0x3b, (byte) 0xe8, (byte) 0x25, (byte) 0x0c, (byte) 0x46, (byte) 0xf0, - (byte) 0x6d, (byte) 0xce, (byte) 0xea, (byte) 0x3a, (byte) 0x7f, (byte) 0xa1, (byte) 0x34, (byte) 0x80, - (byte) 0x57, (byte) 0xe2, (byte) 0xf6, (byte) 0x55, (byte) 0x6a, (byte) 0xd6, (byte) 0xb1, (byte) 0x31, - (byte) 0x8a, (byte) 0x02, (byte) 0x4a, (byte) 0x83, (byte) 0x8f, (byte) 0x21, (byte) 0xaf, (byte) 0x1f, - (byte) 0xde, (byte) 0x04, (byte) 0x89, (byte) 0x77, (byte) 0xeb, (byte) 0x48, (byte) 0xf5, (byte) 0x9f, - (byte) 0xfd, (byte) 0x49, (byte) 0x24, (byte) 0xca, (byte) 0x1c, (byte) 0x60, (byte) 0x90, (byte) 0x2e, - (byte) 0x52, (byte) 0xf0, (byte) 0xa0, (byte) 0x89, (byte) 0xbc, (byte) 0x76, (byte) 0x89, (byte) 0x70, - (byte) 0x40, (byte) 0xe0, (byte) 0x82, (byte) 0xf9, (byte) 0x37, (byte) 0x76, (byte) 0x38, (byte) 0x48, - (byte) 0x64, (byte) 0x5e, (byte) 0x07, (byte) 0x05 }; - - byte c_expected[] = { (byte) 0xf3, (byte) 0xff, (byte) 0xc7, (byte) 0x70, (byte) 0x3f, (byte) 0x94, (byte) 0x00, - (byte) 0xe5, (byte) 0x2a, (byte) 0x7d, (byte) 0xfb, (byte) 0x4b, (byte) 0x3d, (byte) 0x33, (byte) 0x05, - (byte) 0xd9, (byte) 0x8e, (byte) 0x99, (byte) 0x3b, (byte) 0x9f, (byte) 0x48, (byte) 0x68, (byte) 0x12, - (byte) 0x73, (byte) 0xc2, (byte) 0x96, (byte) 0x50, (byte) 0xba, (byte) 0x32, (byte) 0xfc, (byte) 0x76, - (byte) 0xce, (byte) 0x48, (byte) 0x33, (byte) 0x2e, (byte) 0xa7, (byte) 0x16, (byte) 0x4d, (byte) 0x96, - (byte) 0xa4, (byte) 0x47, (byte) 0x6f, (byte) 0xb8, (byte) 0xc5, (byte) 0x31, (byte) 0xa1, (byte) 0x18, - (byte) 0x6a, (byte) 0xc0, (byte) 0xdf, (byte) 0xc1, (byte) 0x7c, (byte) 0x98, (byte) 0xdc, (byte) 0xe8, - (byte) 0x7b, (byte) 0x4d, (byte) 0xa7, (byte) 0xf0, (byte) 0x11, (byte) 0xec, (byte) 0x48, (byte) 0xc9, - (byte) 0x72, (byte) 0x71, (byte) 0xd2, (byte) 0xc2, (byte) 0x0f, (byte) 0x9b, (byte) 0x92, (byte) 0x8f, - (byte) 0xe2, (byte) 0x27, (byte) 0x0d, (byte) 0x6f, (byte) 0xb8, (byte) 0x63, (byte) 0xd5, (byte) 0x17, - (byte) 0x38, (byte) 0xb4, (byte) 0x8e, (byte) 0xee, (byte) 0xe3, (byte) 0x14, (byte) 0xa7, (byte) 0xcc, - (byte) 0x8a, (byte) 0xb9, (byte) 0x32, (byte) 0x16, (byte) 0x45, (byte) 0x48, (byte) 0xe5, (byte) 0x26, - (byte) 0xae, (byte) 0x90, (byte) 0x22, (byte) 0x43, (byte) 0x68, (byte) 0x51, (byte) 0x7a, (byte) 0xcf, - (byte) 0xea, (byte) 0xbd, (byte) 0x6b, (byte) 0xb3, (byte) 0x73, (byte) 0x2b, (byte) 0xc0, (byte) 0xe9, - (byte) 0xda, (byte) 0x99, (byte) 0x83, (byte) 0x2b, (byte) 0x61, (byte) 0xca, (byte) 0x01, (byte) 0xb6, - (byte) 0xde, (byte) 0x56, (byte) 0x24, (byte) 0x4a, (byte) 0x9e, (byte) 0x88, (byte) 0xd5, (byte) 0xf9, - (byte) 0xb3, (byte) 0x79, (byte) 0x73, (byte) 0xf6, (byte) 0x22, (byte) 0xa4, (byte) 0x3d, (byte) 0x14, - (byte) 0xa6, (byte) 0x59, (byte) 0x9b, (byte) 0x1f, (byte) 0x65, (byte) 0x4c, (byte) 0xb4, (byte) 0x5a, - (byte) 0x74, (byte) 0xe3, (byte) 0x55, (byte) 0xa5 }; - - /* encrypt data and compare with expected result */ - NaCl nacl = new NaCl(alicesk, bobpk); - byte[] c = nacl.encrypt(m, nonce); - if (!Arrays.equals(c, c_expected)) - throw new RuntimeException("Crypto self-test failed (1)"); - - /* decrypt data and compare with plaintext */ - nacl = new NaCl(bobsk, alicepk); - byte[] p_d = nacl.decrypt(c, nonce); - if (!Arrays.equals(p_d, m)) - throw new RuntimeException("Crypto self-test failed (2)"); - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519.java b/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519.java deleted file mode 100644 index 3079e04..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519.java +++ /dev/null @@ -1,466 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl.crypto; - -public class curve25519 -{ - final int CRYPTO_BYTES = 32; - final int CRYPTO_SCALARBYTES = 32; - - static byte[] basev = { 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - static int[] minusp = { 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128 }; - - public static int crypto_scalarmult_base(byte[] q, byte[] n) - { - byte[] basevp = basev; - return crypto_scalarmult(q, n, basevp); - } - - static void add(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) - { - int u = 0; - - for (int j = 0; j < 31; ++j) - { - u += a[aoffset + j] + b[boffset + j]; - outv[outvoffset + j] = u & 255; - u >>>= 8; - } - - u += a[aoffset + 31] + b[boffset + 31]; - outv[outvoffset + 31] = u; - } - - static void sub(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) - { - int u = 218; - - for (int j = 0; j < 31; ++j) - { - u += a[aoffset + j] + 65280 - b[boffset + j]; - outv[outvoffset + j] = u & 255; - u >>>= 8; - } - - u += a[aoffset + 31] - b[boffset + 31]; - outv[outvoffset + 31] = u; - } - - static void squeeze(int[] a, int aoffset) - { - int u = 0; - - for (int j = 0; j < 31; ++j) - { - u += a[aoffset + j]; - a[aoffset + j] = u & 255; - u >>>= 8; - } - - u += a[aoffset + 31]; - a[aoffset + 31] = u & 127; - u = 19 * (u >>> 7); - - for (int j = 0; j < 31; ++j) - { - u += a[aoffset + j]; - a[aoffset + j] = u & 255; - u >>>= 8; - } - - u += a[aoffset + 31]; - a[aoffset + 31] = u; - } - - static void freeze(int[] a, int aoffset) - { - int[] aorig = new int[32]; - - for (int j = 0; j < 32; ++j) - aorig[j] = a[aoffset + j]; - - int[] minuspp = minusp; - - add(a, 0, a, 0, minuspp, 0); - - int negative = (int) (-((a[aoffset + 31] >>> 7) & 1)); - - for (int j = 0; j < 32; ++j) - a[aoffset + j] ^= negative & (aorig[j] ^ a[aoffset + j]); - } - - static void mult(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) - { - int j; - - for (int i = 0; i < 32; ++i) - { - int u = 0; - - for (j = 0; j <= i; ++j) - u += a[aoffset + j] * b[boffset + i - j]; - - for (j = i + 1; j < 32; ++j) - u += 38 * a[aoffset + j] * b[boffset + i + 32 - j]; - - outv[outvoffset + i] = u; - } - - squeeze(outv, outvoffset); - } - - static void mult121665(int[] outv, int[] a) - { - int j; - int u = 0; - - for (j = 0; j < 31; ++j) - { - u += 121665 * a[j]; - outv[j] = u & 255; - u >>>= 8; - } - - u += 121665 * a[31]; - outv[31] = u & 127; - u = 19 * (u >>> 7); - - for (j = 0; j < 31; ++j) - { - u += outv[j]; - outv[j] = u & 255; - u >>>= 8; - } - - u += outv[j]; - outv[j] = u; - } - - static void square(int[] outv, int outvoffset, int[] a, int aoffset) - { - int j; - - for (int i = 0; i < 32; ++i) - { - int u = 0; - - for (j = 0; j < i - j; ++j) - u += a[aoffset + j] * a[aoffset + i - j]; - - for (j = i + 1; j < i + 32 - j; ++j) - u += 38 * a[aoffset + j] * a[aoffset + i + 32 - j]; - - u *= 2; - - if ((i & 1) == 0) - { - u += a[aoffset + i / 2] * a[aoffset + i / 2]; - u += 38 * a[aoffset + i / 2 + 16] * a[aoffset + i / 2 + 16]; - } - - outv[outvoffset + i] = u; - } - - squeeze(outv, outvoffset); - } - - static void select(int[] p, int[] q, int[] r, int[] s, int b) - { - int bminus1 = b - 1; - - for (int j = 0; j < 64; ++j) - { - int t = bminus1 & (r[j] ^ s[j]); - p[j] = s[j] ^ t; - q[j] = r[j] ^ t; - } - } - - static void mainloop(int[] work, byte[] e) - { - int[] xzm1 = new int[64]; - int[] xzm = new int[64]; - int[] xzmb = new int[64]; - int[] xzm1b = new int[64]; - int[] xznb = new int[64]; - int[] xzn1b = new int[64]; - int[] a0 = new int[64]; - int[] a1 = new int[64]; - int[] b0 = new int[64]; - int[] b1 = new int[64]; - int[] c1 = new int[64]; - int[] r = new int[32]; - int[] s = new int[32]; - int[] t = new int[32]; - int[] u = new int[32]; - - for (int j = 0; j < 32; ++j) - xzm1[j] = work[j]; - - xzm1[32] = 1; - - for (int j = 33; j < 64; ++j) - xzm1[j] = 0; - - xzm[0] = 1; - - for (int j = 1; j < 64; ++j) - xzm[j] = 0; - - int[] xzmbp = xzmb, a0p = a0, xzm1bp = xzm1b; - int[] a1p = a1, b0p = b0, b1p = b1, c1p = c1; - int[] xznbp = xznb, up = u, xzn1bp = xzn1b; - int[] workp = work, sp = s, rp = r; - - for (int pos = 254; pos >= 0; --pos) - { - int b = ((int) ((e[pos / 8] & 0xFF) >>> (pos & 7))); - b &= 1; - select(xzmb, xzm1b, xzm, xzm1, b); - add(a0, 0, xzmb, 0, xzmbp, 32); - sub(a0p, 32, xzmb, 0, xzmbp, 32); - add(a1, 0, xzm1b, 0, xzm1bp, 32); - sub(a1p, 32, xzm1b, 0, xzm1bp, 32); - square(b0p, 0, a0p, 0); - square(b0p, 32, a0p, 32); - mult(b1p, 0, a1p, 0, a0p, 32); - mult(b1p, 32, a1p, 32, a0p, 0); - add(c1, 0, b1, 0, b1p, 32); - sub(c1p, 32, b1, 0, b1p, 32); - square(rp, 0, c1p, 32); - sub(sp, 0, b0, 0, b0p, 32); - mult121665(t, s); - add(u, 0, t, 0, b0p, 0); - mult(xznbp, 0, b0p, 0, b0p, 32); - mult(xznbp, 32, sp, 0, up, 0); - square(xzn1bp, 0, c1p, 0); - mult(xzn1bp, 32, rp, 0, workp, 0); - select(xzm, xzm1, xznb, xzn1b, b); - } - - for (int j = 0; j < 64; ++j) - work[j] = xzm[j]; - } - - static void recip(int[] outv, int outvoffset, int[] z, int zoffset) - { - int[] z2 = new int[32]; - int[] z9 = new int[32]; - int[] z11 = new int[32]; - int[] z2_5_0 = new int[32]; - int[] z2_10_0 = new int[32]; - int[] z2_20_0 = new int[32]; - int[] z2_50_0 = new int[32]; - int[] z2_100_0 = new int[32]; - int[] t0 = new int[32]; - int[] t1 = new int[32]; - - /* 2 */ - int[] z2p = z2; - square(z2p, 0, z, zoffset); - - /* 4 */ - square(t1, 0, z2, 0); - - /* 8 */ - square(t0, 0, t1, 0); - - /* 9 */ - int[] z9p = z9, t0p = t0; - mult(z9p, 0, t0p, 0, z, zoffset); - - /* 11 */ - mult(z11, 0, z9, 0, z2, 0); - - /* 22 */ - square(t0, 0, z11, 0); - - /* 2^5 - 2^0 = 31 */ - mult(z2_5_0, 0, t0, 0, z9, 0); - - /* 2^6 - 2^1 */ - square(t0, 0, z2_5_0, 0); - - /* 2^7 - 2^2 */ - square(t1, 0, t0, 0); - - /* 2^8 - 2^3 */ - square(t0, 0, t1, 0); - - /* 2^9 - 2^4 */ - square(t1, 0, t0, 0); - - /* 2^10 - 2^5 */ - square(t0, 0, t1, 0); - - /* 2^10 - 2^0 */ - mult(z2_10_0, 0, t0, 0, z2_5_0, 0); - - /* 2^11 - 2^1 */ - square(t0, 0, z2_10_0, 0); - - /* 2^12 - 2^2 */ - square(t1, 0, t0, 0); - - /* 2^20 - 2^10 */ - for (int i = 2; i < 10; i += 2) - { - square(t0, 0, t1, 0); - square(t1, 0, t0, 0); - } - - /* 2^20 - 2^0 */ - mult(z2_20_0, 0, t1, 0, z2_10_0, 0); - - /* 2^21 - 2^1 */ - square(t0, 0, z2_20_0, 0); - - /* 2^22 - 2^2 */ - square(t1, 0, t0, 0); - - /* 2^40 - 2^20 */ - for (int i = 2; i < 20; i += 2) - { - square(t0, 0, t1, 0); - square(t1, 0, t0, 0); - } - - /* 2^40 - 2^0 */ - mult(t0, 0, t1, 0, z2_20_0, 0); - - /* 2^41 - 2^1 */ - square(t1, 0, t0, 0); - - /* 2^42 - 2^2 */ - square(t0, 0, t1, 0); - - /* 2^50 - 2^10 */ - for (int i = 2; i < 10; i += 2) - { - square(t1, 0, t0, 0); - square(t0, 0, t1, 0); - } - - /* 2^50 - 2^0 */ - mult(z2_50_0, 0, t0, 0, z2_10_0, 0); - - /* 2^51 - 2^1 */ - square(t0, 0, z2_50_0, 0); - - /* 2^52 - 2^2 */ - square(t1, 0, t0, 0); - - /* 2^100 - 2^50 */ - for (int i = 2; i < 50; i += 2) - { - square(t0, 0, t1, 0); - square(t1, 0, t0, 0); - } - - /* 2^100 - 2^0 */ - mult(z2_100_0, 0, t1, 0, z2_50_0, 0); - - /* 2^101 - 2^1 */ - square(t1, 0, z2_100_0, 0); - - /* 2^102 - 2^2 */ - square(t0, 0, t1, 0); - - /* 2^200 - 2^100 */ - for (int i = 2; i < 100; i += 2) - { - square(t1, 0, t0, 0); - square(t0, 0, t1, 0); - } - - /* 2^200 - 2^0 */ - mult(t1, 0, t0, 0, z2_100_0, 0); - - /* 2^201 - 2^1 */ - square(t0, 0, t1, 0); - - /* 2^202 - 2^2 */ - square(t1, 0, t0, 0); - - /* 2^250 - 2^50 */ - for (int i = 2; i < 50; i += 2) - { - square(t0, 0, t1, 0); - square(t1, 0, t0, 0); - } - - /* 2^250 - 2^0 */ - mult(t0, 0, t1, 0, z2_50_0, 0); - - /* 2^251 - 2^1 */ - square(t1, 0, t0, 0); - - /* 2^252 - 2^2 */ - square(t0, 0, t1, 0); - - /* 2^253 - 2^3 */ - square(t1, 0, t0, 0); - - /* 2^254 - 2^4 */ - square(t0, 0, t1, 0); - - /* 2^255 - 2^5 */ - square(t1, 0, t0, 0); - - /* 2^255 - 21 */ - int[] t1p = t1, z11p = z11; - mult(outv, outvoffset, t1p, 0, z11p, 0); - } - - public static int crypto_scalarmult(byte[] q, byte[] n, byte[] p) - { - int[] work = new int[96]; - byte[] e = new byte[32]; - - for (int i = 0; i < 32; ++i) - e[i] = n[i]; - - e[0] &= 248; - e[31] &= 127; - e[31] |= 64; - - for (int i = 0; i < 32; ++i) - work[i] = p[i] & 0xFF; - - mainloop(work, e); - - recip(work, 32, work, 32); - mult(work, 64, work, 0, work, 32); - freeze(work, 64); - - for (int i = 0; i < 32; ++i) - q[i] = (byte) work[64 + i]; - - return 0; - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519xsalsa20poly1305.java b/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519xsalsa20poly1305.java deleted file mode 100644 index b952deb..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519xsalsa20poly1305.java +++ /dev/null @@ -1,110 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl.crypto; - -public class curve25519xsalsa20poly1305 -{ - public static final int crypto_box_PUBLICKEYBYTES = 32; - public static final int crypto_box_SECRETKEYBYTES = 32; - public static final int crypto_box_BEFORENMBYTES = 32; - public static final int crypto_box_NONCEBYTES = 24; - public static final int crypto_box_ZEROBYTES = 32; - public static final int crypto_box_BOXZEROBYTES = 16; - - public static int crypto_box_getpublickey(byte[] pk, byte[] sk) - { - return curve25519.crypto_scalarmult_base(pk, sk); - } - - public static int crypto_box_afternm(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) - { - return xsalsa20poly1305.crypto_secretbox(c, m, mlen, n, k); - } - - public static int crypto_box_afternm_nopad(byte[] c, int coffset, byte[] m, int moffset, long mlen, byte[] n, byte[] k) - { - return xsalsa20poly1305.crypto_secretbox_nopad(c, coffset, m, moffset, mlen, n, k); - } - - public static int crypto_box_beforenm(byte[] k, byte[] pk, byte[] sk) - { - byte[] s = new byte[32]; - byte[] sp = s, sigmap = xsalsa20.sigma; - - curve25519.crypto_scalarmult(sp, sk, pk); - return hsalsa20.crypto_core(k, null, sp, sigmap); - } - - public static int crypto_box(byte[] c, byte[] m, long mlen, byte[] n, byte[] pk, byte[] sk) - { - byte[] k = new byte[crypto_box_BEFORENMBYTES]; - byte[] kp = k; - - crypto_box_beforenm(kp, pk, sk); - return crypto_box_afternm(c, m, mlen, n, kp); - } - - public static int crypto_box_open(byte[] m, byte[] c, long clen, byte[] n, byte[] pk, byte[] sk) - { - byte[] k = new byte[crypto_box_BEFORENMBYTES]; - byte[] kp = k; - - crypto_box_beforenm(kp, pk, sk); - return crypto_box_open_afternm(m, c, clen, n, kp); - } - - public static int crypto_box_open_afternm(byte[] m, byte[] c, long clen, byte[] n, byte[] k) - { - return xsalsa20poly1305.crypto_secretbox_open(m, c, clen, n, k); - } - - public static int crypto_box_open_afternm_nopad(byte[] m, int moffset, byte[] c, int coffset, long clen, byte[] n, byte[] k) - { - return xsalsa20poly1305.crypto_secretbox_open_nopad(m, moffset, c, coffset, clen, n, k); - } - - public static int crypto_box_afternm(byte[] c, byte[] m, byte[] n, byte[] k) - { - return crypto_box_afternm(c, m, (long)m.length, n, k); - } - - public static int crypto_box_open_afternm(byte[] m, byte[] c, byte[] n, byte[] k) - { - return crypto_box_open_afternm(m, c, (long) c.length, n, k); - } - - public static int crypto_box(byte[] c, byte[] m, byte[] n, byte[] pk, byte[] sk) - { - return crypto_box(c, m, (long) m.length, n, pk, sk); - } - - public static int crypto_box_open(byte[] m, byte[] c, byte[] n, byte[] pk, byte[] sk) - { - return crypto_box_open(m, c, (long) c.length, n, pk, sk); - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/hsalsa20.java b/source/src/main/java/com/neilalexander/jnacl/crypto/hsalsa20.java deleted file mode 100644 index ae5e429..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/hsalsa20.java +++ /dev/null @@ -1,164 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl.crypto; - -public class hsalsa20 -{ - static final int ROUNDS = 20; - - static int rotate(int u, int c) - { - return (u << c) | (u >>> (32 - c)); - } - - static int load_littleendian(byte[] x, int offset) - { - return ((int)(x[offset])&0xff) | - ((((int)(x[offset + 1])&0xff)) << 8) | - ((((int)(x[offset + 2])&0xff)) << 16) | - ((((int)(x[offset + 3])&0xff)) << 24); - } - - static void store_littleendian(byte[] x, int offset, int u) - { - x[offset] = (byte) u; u >>>= 8; - x[offset + 1] = (byte) u; u >>>= 8; - x[offset + 2] = (byte) u; u >>>= 8; - x[offset + 3] = (byte) u; - } - - public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) - { - int x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; - int j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - int i; - - j0 = x0 = load_littleendian(c, 0); - j1 = x1 = load_littleendian(k, 0); - j2 = x2 = load_littleendian(k, 4); - j3 = x3 = load_littleendian(k, 8); - j4 = x4 = load_littleendian(k, 12); - j5 = x5 = load_littleendian(c, 4); - - if (inv != null) - { - j6 = x6 = load_littleendian(inv, 0); - j7 = x7 = load_littleendian(inv, 4); - j8 = x8 = load_littleendian(inv, 8); - j9 = x9 = load_littleendian(inv, 12); - } - else - { - j6 = x6 = j7 = x7 = j8 = x8 = j9 = x9 = 0; - } - - j10 = x10 = load_littleendian(c, 8); - j11 = x11 = load_littleendian(k, 16); - j12 = x12 = load_littleendian(k, 20); - j13 = x13 = load_littleendian(k, 24); - j14 = x14 = load_littleendian(k, 28); - j15 = x15 = load_littleendian(c, 12); - - for (i = ROUNDS; i > 0; i -= 2) - { - x4 ^= rotate(x0 + x12, 7); - x8 ^= rotate(x4 + x0, 9); - x12 ^= rotate(x8 + x4, 13); - x0 ^= rotate(x12 + x8, 18); - x9 ^= rotate(x5 + x1, 7); - x13 ^= rotate(x9 + x5, 9); - x1 ^= rotate(x13 + x9, 13); - x5 ^= rotate(x1 + x13, 18); - x14 ^= rotate(x10 + x6, 7); - x2 ^= rotate(x14 + x10, 9); - x6 ^= rotate(x2 + x14, 13); - x10 ^= rotate(x6 + x2, 18); - x3 ^= rotate(x15 + x11, 7); - x7 ^= rotate(x3 + x15, 9); - x11 ^= rotate(x7 + x3, 13); - x15 ^= rotate(x11 + x7, 18); - x1 ^= rotate(x0 + x3, 7); - x2 ^= rotate(x1 + x0, 9); - x3 ^= rotate(x2 + x1, 13); - x0 ^= rotate(x3 + x2, 18); - x6 ^= rotate(x5 + x4, 7); - x7 ^= rotate(x6 + x5, 9); - x4 ^= rotate(x7 + x6, 13); - x5 ^= rotate(x4 + x7, 18); - x11 ^= rotate(x10 + x9, 7); - x8 ^= rotate(x11 + x10, 9); - x9 ^= rotate(x8 + x11, 13); - x10 ^= rotate(x9 + x8, 18); - x12 ^= rotate(x15 + x14, 7); - x13 ^= rotate(x12 + x15, 9); - x14 ^= rotate(x13 + x12, 13); - x15 ^= rotate(x14 + x13, 18); - } - - x0 += j0; - x1 += j1; - x2 += j2; - x3 += j3; - x4 += j4; - x5 += j5; - x6 += j6; - x7 += j7; - x8 += j8; - x9 += j9; - x10 += j10; - x11 += j11; - x12 += j12; - x13 += j13; - x14 += j14; - x15 += j15; - - x0 -= load_littleendian(c, 0); - x5 -= load_littleendian(c, 4); - x10 -= load_littleendian(c, 8); - x15 -= load_littleendian(c, 12); - - if (inv != null) - { - x6 -= load_littleendian(inv, 0); - x7 -= load_littleendian(inv, 4); - x8 -= load_littleendian(inv, 8); - x9 -= load_littleendian(inv, 12); - } - - store_littleendian(outv, 0, x0); - store_littleendian(outv, 4, x5); - store_littleendian(outv, 8, x10); - store_littleendian(outv, 12, x15); - store_littleendian(outv, 16, x6); - store_littleendian(outv, 20, x7); - store_littleendian(outv, 24, x8); - store_littleendian(outv, 28, x9); - - return 0; - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/poly1305.java b/source/src/main/java/com/neilalexander/jnacl/crypto/poly1305.java deleted file mode 100644 index 7db9951..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/poly1305.java +++ /dev/null @@ -1,178 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl.crypto; - -public class poly1305 -{ - final int CRYPTO_BYTES = 16; - final int CRYPTO_KEYBYTES = 32; - - static final int[] minusp = {5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252}; - - public static int crypto_onetimeauth_verify(byte[] h, int hoffset, byte[] inv, int invoffset, long inlen, byte[] k) - { - byte[] correct = new byte[16]; - - crypto_onetimeauth(correct, 0, inv, invoffset, inlen, k); - return verify_16.crypto_verify(h, hoffset, correct); - } - - static void add(int[] h, int[] c) - { - int j; - int u = 0; - - for (j = 0; j < 17; ++j) - { - u += h[j] + c[j]; - h[j] = u & 255; - u >>>= 8; - } - } - - static void squeeze(int[] h) - { - int u = 0; - - for (int j = 0; j < 16; ++j) - { - u += h[j]; - h[j] = u & 255; - u >>>= 8; - } - - u += h[16]; - h[16] = u & 3; - u = 5 * (u >>> 2); - - for (int j = 0; j < 16; ++j) - { - u += h[j]; - h[j] = u & 255; - u >>>= 8; - } - - u += h[16]; - h[16] = u; - } - - static void freeze(int[] h) - { - int[] horig = new int[17]; - - for (int j = 0; j < 17; ++j) - horig[j] = h[j]; - - add(h, minusp); - - int negative = (int)(-(h[16] >>> 7)); - - for (int j = 0; j < 17; ++j) - h[j] ^= negative & (horig[j] ^ h[j]); - } - - static void mulmod(int[] h, int[] r) - { - int[] hr = new int[17]; - - for (int i = 0; i < 17; ++i) - { - int u = 0; - - for (int j = 0; j <= i; ++j) - u += h[j] * r[i - j]; - - for (int j = i + 1; j < 17; ++j) - u += 320 * h[j] * r[i + 17 - j]; - - hr[i] = u; - } - - for (int i = 0; i < 17; ++i) - h[i] = hr[i]; - - squeeze(h); - } - - public static int crypto_onetimeauth(byte[] outv, int outvoffset, byte[] inv, int invoffset, long inlen, byte[] k) - { - int j; - int[] r = new int[17]; - int[] h = new int[17]; - int[] c = new int[17]; - - r[0] = k[0] & 0xFF; - r[1] = k[1] & 0xFF; - r[2] = k[2] & 0xFF; - r[3] = k[3] & 15; - r[4] = k[4] & 252; - r[5] = k[5] & 0xFF; - r[6] = k[6] & 0xFF; - r[7] = k[7] & 15; - r[8] = k[8] & 252; - r[9] = k[9] & 0xFF; - r[10] = k[10] & 0xFF; - r[11] = k[11] & 15; - r[12] = k[12] & 252; - r[13] = k[13] & 0xFF; - r[14] = k[14] & 0xFF; - r[15] = k[15] & 15; - r[16] = 0; - - for (j = 0; j < 17; ++j) - h[j] = 0; - - while (inlen > 0) - { - for (j = 0; j < 17; ++j) - c[j] = 0; - - for (j = 0; (j < 16) && (j < inlen); ++j) - c[j] = inv[invoffset + j]&0xff; - - c[j] = 1; - invoffset += j; - inlen -= j; - add(h, c); - mulmod(h, r); - } - - freeze(h); - - for (j = 0; j < 16; ++j) - c[j] = k[j + 16] & 0xFF; - - c[16] = 0; - add(h, c); - - for (j = 0; j < 16; ++j) - outv[j + outvoffset] = (byte)h[j]; - - return 0; - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/salsa20.java b/source/src/main/java/com/neilalexander/jnacl/crypto/salsa20.java deleted file mode 100644 index 62c58d3..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/salsa20.java +++ /dev/null @@ -1,324 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl.crypto; - -public class salsa20 -{ - final int crypto_core_salsa20_ref_OUTPUTBYTES = 64; - final int crypto_core_salsa20_ref_INPUTBYTES = 16; - final int crypto_core_salsa20_ref_KEYBYTES = 32; - final int crypto_core_salsa20_ref_CONSTBYTES = 16; - final int crypto_stream_salsa20_ref_KEYBYTES = 32; - final int crypto_stream_salsa20_ref_NONCEBYTES = 8; - - final static int ROUNDS = 20; - - static long rotate(int u, int c) - { - return (u << c) | (u >>> (32 - c)); - } - - static int load_littleendian(byte[] x, int offset) - { - return ((int)(x[offset])&0xff) | - ((((int)(x[offset + 1])&0xff)) << 8) | - ((((int)(x[offset + 2])&0xff)) << 16) | - ((((int)(x[offset + 3])&0xff)) << 24); - } - - static void store_littleendian(byte[] x, int offset, int u) - { - x[offset] = (byte) u; u >>>= 8; - x[offset + 1] = (byte) u; u >>>= 8; - x[offset + 2] = (byte) u; u >>>= 8; - x[offset + 3] = (byte) u; - } - - public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) - { - int x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; - int j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - int i; - - j0 = x0 = load_littleendian(c, 0); - j1 = x1 = load_littleendian(k, 0); - j2 = x2 = load_littleendian(k, 4); - j3 = x3 = load_littleendian(k, 8); - j4 = x4 = load_littleendian(k, 12); - j5 = x5 = load_littleendian(c, 4); - j6 = x6 = load_littleendian(inv, 0); - j7 = x7 = load_littleendian(inv, 4); - j8 = x8 = load_littleendian(inv, 8); - j9 = x9 = load_littleendian(inv, 12); - j10 = x10 = load_littleendian(c, 8); - j11 = x11 = load_littleendian(k, 16); - j12 = x12 = load_littleendian(k, 20); - j13 = x13 = load_littleendian(k, 24); - j14 = x14 = load_littleendian(k, 28); - j15 = x15 = load_littleendian(c, 12); - - for (i = ROUNDS; i > 0; i -= 2) - { - x4 ^= rotate(x0 + x12, 7); - x8 ^= rotate(x4 + x0, 9); - x12 ^= rotate(x8 + x4, 13); - x0 ^= rotate(x12 + x8, 18); - x9 ^= rotate(x5 + x1, 7); - x13 ^= rotate(x9 + x5, 9); - x1 ^= rotate(x13 + x9, 13); - x5 ^= rotate(x1 + x13, 18); - x14 ^= rotate(x10 + x6, 7); - x2 ^= rotate(x14 + x10, 9); - x6 ^= rotate(x2 + x14, 13); - x10 ^= rotate(x6 + x2, 18); - x3 ^= rotate(x15 + x11, 7); - x7 ^= rotate(x3 + x15, 9); - x11 ^= rotate(x7 + x3, 13); - x15 ^= rotate(x11 + x7, 18); - x1 ^= rotate(x0 + x3, 7); - x2 ^= rotate(x1 + x0, 9); - x3 ^= rotate(x2 + x1, 13); - x0 ^= rotate(x3 + x2, 18); - x6 ^= rotate(x5 + x4, 7); - x7 ^= rotate(x6 + x5, 9); - x4 ^= rotate(x7 + x6, 13); - x5 ^= rotate(x4 + x7, 18); - x11 ^= rotate(x10 + x9, 7); - x8 ^= rotate(x11 + x10, 9); - x9 ^= rotate(x8 + x11, 13); - x10 ^= rotate(x9 + x8, 18); - x12 ^= rotate(x15 + x14, 7); - x13 ^= rotate(x12 + x15, 9); - x14 ^= rotate(x13 + x12, 13); - x15 ^= rotate(x14 + x13, 18); - } - - x0 += j0; - x1 += j1; - x2 += j2; - x3 += j3; - x4 += j4; - x5 += j5; - x6 += j6; - x7 += j7; - x8 += j8; - x9 += j9; - x10 += j10; - x11 += j11; - x12 += j12; - x13 += j13; - x14 += j14; - x15 += j15; - - store_littleendian(outv, 0, x0); - store_littleendian(outv, 4, x1); - store_littleendian(outv, 8, x2); - store_littleendian(outv, 12, x3); - store_littleendian(outv, 16, x4); - store_littleendian(outv, 20, x5); - store_littleendian(outv, 24, x6); - store_littleendian(outv, 28, x7); - store_littleendian(outv, 32, x8); - store_littleendian(outv, 36, x9); - store_littleendian(outv, 40, x10); - store_littleendian(outv, 44, x11); - store_littleendian(outv, 48, x12); - store_littleendian(outv, 52, x13); - store_littleendian(outv, 56, x14); - store_littleendian(outv, 60, x15); - - return 0; - } - - public static int crypto_stream(byte[] c, int clen, byte[] n, int noffset, byte[] k) - { - byte[] inv = new byte[16]; - byte[] block = new byte[64]; - - int coffset = 0; - - if (clen == 0) - return 0; - - for (int i = 0; i < 8; ++i) - inv[i] = n[noffset + i]; - - for (int i = 8; i < 16; ++i) - inv[i] = 0; - - while (clen >= 64) - { - salsa20.crypto_core(c, inv, k, xsalsa20.sigma); - - int u = 1; - - for (int i = 8; i < 16; ++i) - { - u += inv[i]&0xff; - inv[i] = (byte) u; - u >>>= 8; - } - - clen -= 64; - coffset += 64; - } - - if (clen != 0) - { - salsa20.crypto_core(block, inv, k, xsalsa20.sigma); - - for (int i = 0; i < clen; ++i) - c[coffset + i] = block[i]; - } - - return 0; - } - - public static int crypto_stream_xor(byte[] c, byte[] m, int mlen, byte[] n, int noffset, byte[] k) - { - byte[] inv = new byte[16]; - byte[] block = new byte[64]; - - int coffset = 0; - int moffset = 0; - - if (mlen == 0) - return 0; - - for (int i = 0; i < 8; ++i) - inv[i] = n[noffset + i]; - - for (int i = 8; i < 16; ++i) - inv[i] = 0; - - while (mlen >= 64) - { - salsa20.crypto_core(block, inv, k, xsalsa20.sigma); - - for (int i = 0; i < 64; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ block[i]); - - int u = 1; - - for (int i = 8; i < 16; ++i) - { - u += inv[i]&0xff; - inv[i] = (byte) u; - u >>>= 8; - } - - mlen -= 64; - coffset += 64; - moffset += 64; - } - - if (mlen != 0) - { - salsa20.crypto_core(block, inv, k, xsalsa20.sigma); - - for (int i = 0; i < mlen; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ block[i]); - } - - return 0; - } - - public static int crypto_stream_xor_skip32(byte[] c0, byte[] c, int coffset, byte[] m, int moffset, int mlen, byte[] n, int noffset, byte[] k) - { - /* Variant of crypto_stream_xor that outputs the first 32 bytes of the cipherstream to c0 */ - - int u; - byte[] inv = new byte[16]; - byte[] prevblock = new byte[64]; - byte[] curblock = new byte[64]; - - if (mlen == 0) - return 0; - - for (int i = 0; i < 8; ++i) - inv[i] = n[noffset + i]; - - for (int i = 8; i < 16; ++i) - inv[i] = 0; - - /* calculate first block */ - salsa20.crypto_core(prevblock, inv, k, xsalsa20.sigma); - - /* extract first 32 bytes of cipherstream into c0 */ - if (c0 != null) - System.arraycopy(prevblock, 0, c0, 0, 32); - - while (mlen >= 64) - { - u = 1; - for (int i = 8; i < 16; ++i) - { - u += inv[i]&0xff; - inv[i] = (byte) u; - u >>>= 8; - } - - salsa20.crypto_core(curblock, inv, k, xsalsa20.sigma); - - for (int i = 0; i < 32; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ prevblock[i+32]); - - for (int i = 32; i < 64; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ curblock[i-32]); - - mlen -= 64; - coffset += 64; - moffset += 64; - - byte[] tmpblock = prevblock; - prevblock = curblock; - curblock = tmpblock; - } - - if (mlen != 0) - { - u = 1; - for (int i = 8; i < 16; ++i) - { - u += inv[i]&0xff; - inv[i] = (byte) u; - u >>>= 8; - } - - salsa20.crypto_core(curblock, inv, k, xsalsa20.sigma); - - for (int i = 0; i < mlen && i < 32; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ prevblock[i+32]); - - for (int i = 32; i < mlen && i < 64; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ curblock[i-32]); - } - - return 0; - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/verify_16.java b/source/src/main/java/com/neilalexander/jnacl/crypto/verify_16.java deleted file mode 100644 index 443c641..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/verify_16.java +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl.crypto; - -public class verify_16 -{ - final int crypto_verify_16_ref_BYTES = 16; - - public static int crypto_verify(byte[] x, int xoffset, byte[] y) - { - int differentbits = 0; - - for (int i = 0; i < 15; i++) - differentbits |= ((int)(x[xoffset + i] ^ y[i])) & 0xff; - - return (1 & (((int)differentbits - 1) >>> 8)) - 1; - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20.java b/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20.java deleted file mode 100644 index f755787..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20.java +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl.crypto; - -public class xsalsa20 -{ - final int crypto_stream_xsalsa20_ref_KEYBYTES = 32; - final int crypto_stream_xsalsa20_ref_NONCEBYTES = 24; - - public final static byte[] sigma = {(byte) 'e', (byte) 'x', (byte) 'p', (byte) 'a', - (byte) 'n', (byte) 'd', (byte) ' ', (byte) '3', - (byte) '2', (byte) '-', (byte) 'b', (byte) 'y', - (byte) 't', (byte) 'e', (byte) ' ', (byte) 'k'}; - - public static int crypto_stream(byte[] c, int clen, byte[] n, byte[] k) - { - byte[] subkey = new byte[32]; - - hsalsa20.crypto_core(subkey, n, k, sigma); - return salsa20.crypto_stream(c, clen, n, 16, subkey); - } - - public static int crypto_stream_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) - { - byte[] subkey = new byte[32]; - - hsalsa20.crypto_core(subkey, n, k, sigma); - return salsa20.crypto_stream_xor(c, m, (int) mlen, n, 16, subkey); - } - - public static int crypto_stream_xor_skip32(byte[] c0, byte[] c, int coffset, byte[] m, int moffset, long mlen, byte[] n, byte[] k) - { - /* Variant of crypto_stream_xor that outputs the first 32 bytes of the cipherstream to c0 */ - - byte[] subkey = new byte[32]; - - hsalsa20.crypto_core(subkey, n, k, sigma); - return salsa20.crypto_stream_xor_skip32(c0, c, coffset, m, moffset, (int) mlen, n, 16, subkey); - } -} diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20poly1305.java b/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20poly1305.java deleted file mode 100644 index 7f94532..0000000 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20poly1305.java +++ /dev/null @@ -1,102 +0,0 @@ -// -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -package com.neilalexander.jnacl.crypto; - -public class xsalsa20poly1305 -{ - final int crypto_secretbox_KEYBYTES = 32; - final int crypto_secretbox_NONCEBYTES = 24; - final int crypto_secretbox_ZEROBYTES = 32; - final int crypto_secretbox_BOXZEROBYTES = 16; - - static public int crypto_secretbox(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) - { - if (mlen < 32) - return -1; - - xsalsa20.crypto_stream_xor(c, m, mlen, n, k); - poly1305.crypto_onetimeauth(c, 16, c, 32, mlen - 32, c); - - for (int i = 0; i < 16; ++i) - c[i] = 0; - - return 0; - } - - static public int crypto_secretbox_nopad(byte[] c, int coffset, byte[] m, int moffset, long mlen, byte[] n, byte[] k) - { - /* variant of crypto_secretbox that doesn't require 32 zero bytes before m and doesn't output - * 16 zero bytes before c */ - byte[] c0 = new byte[32]; - - xsalsa20.crypto_stream_xor_skip32(c0, c, coffset+16, m, moffset, mlen, n, k); - poly1305.crypto_onetimeauth(c, coffset, c, coffset+16, mlen, c0); - - return 0; - } - - static public int crypto_secretbox_open(byte[] m, byte[] c, long clen, byte[] n, byte[] k) - { - if (clen < 32) - return -1; - - byte[] subkeyp = new byte[32]; - - xsalsa20.crypto_stream(subkeyp, 32, n, k); - - if (poly1305.crypto_onetimeauth_verify(c, 16, c, 32, clen - 32, subkeyp) != 0) - return -1; - - xsalsa20.crypto_stream_xor(m, c, clen, n, k); - - for (int i = 0; i < 32; ++i) - m[i] = 0; - - return 0; - } - - static public int crypto_secretbox_open_nopad(byte[] m, int moffset, byte[] c, int coffset, long clen, byte[] n, byte[] k) - { - /* variant of crypto_secretbox_open that doesn't require 16 zero bytes before c and doesn't output - * 32 zero bytes before m */ - - if (clen < 16) - return -1; - - byte[] subkeyp = new byte[32]; - - xsalsa20.crypto_stream(subkeyp, 32, n, k); - - if (poly1305.crypto_onetimeauth_verify(c, coffset, c, coffset+16, clen - 16, subkeyp) != 0) - return -1; - - xsalsa20.crypto_stream_xor_skip32(null, m, moffset, c, coffset+16, clen - 16, n, k); - - return 0; - } -} diff --git a/source/src/test/java/ch/threema/apitool/Common.java b/source/src/test/java/ch/threema/apitool/Common.java deleted file mode 100644 index c3af9a0..0000000 --- a/source/src/test/java/ch/threema/apitool/Common.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -/** - * Common Stuff - */ -public abstract class Common { - - public static final String myPrivateKey = "private:94af3260fa2a19adc8e82e82be598be15bc6ad6f47c8ee303cb185ef860e16d2"; - public static final String myPrivateKeyExtract = "94af3260fa2a19adc8e82e82be598be15bc6ad6f47c8ee303cb185ef860e16d2"; - - public static final String myPublicKey = "public:3851ad59c96146a05b32e41c0ccd0fd639dc8cd66bf6e1cbd3c8d67e4e8f5531"; - public static final String myPublicKeyExtract = "3851ad59c96146a05b32e41c0ccd0fd639dc8cd66bf6e1cbd3c8d67e4e8f5531"; - - public static final String otherPrivateKey = "private:8318e05220acd38e97ba41a9a6318688214219916075ca060f9339a6d1f7fc29"; - public static final String otherPublicKey = "public:10ac7fd937eafb806f9a05bf9afa340a99387b0063cc9cb0d1ea5505d39cc076"; - - public static final String echochoPublicKey = "public:4a6a1b34dcef15d43cb74de2fd36091be99fbbaf126d099d47d83d919712c72b"; - public static final String randomNonce = "516f4f1562dda0704a7bae8997cf0b354c6980181152ac32"; - - public static boolean isEmpty(byte[] byteArray) { - if(byteArray == null) { - return true; - } - else { - for(byte b: byteArray) { - if(b != 0) { - return false; - } - } - } - - return true; - } -} diff --git a/source/src/test/java/ch/threema/apitool/CryptToolTest.java b/source/src/test/java/ch/threema/apitool/CryptToolTest.java deleted file mode 100644 index 141f12f..0000000 --- a/source/src/test/java/ch/threema/apitool/CryptToolTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -import org.junit.Test; - -import com.neilalexander.jnacl.NaCl; - -import ch.threema.apitool.messages.TextMessage; -import ch.threema.apitool.messages.ThreemaMessage; -import ch.threema.apitool.results.EncryptResult; - -public class CryptToolTest { - - @Test - public void testRandomNonce() throws Exception { - byte[] randomNonce = CryptTool.randomNonce(); - - // random nonce should be a byte array - Assert.assertNotNull("random nonce", randomNonce); - - // with a length of 24 - Assert.assertSame("nonce length", randomNonce.length, NaCl.NONCEBYTES); - } - - @Test - public void testCreateKeyPair() { - byte[] privateKey = new byte[NaCl.SECRETKEYBYTES]; - byte[] publicKey = new byte[NaCl.PUBLICKEYBYTES]; - - CryptTool.generateKeyPair(privateKey, publicKey); - - Assert.assertFalse("empty private key", Common.isEmpty(privateKey)); - Assert.assertFalse("empty public key", Common.isEmpty(publicKey)); - } - - @Test - public void testDecrypt() throws Exception { - String nonce = "0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0"; - String box = "45181c7aed95a1c100b1b559116c61b43ce15d04014a805288b7d14bf3a993393264fe554794ce7d6007233e8ef5a0f1ccdd704f34e7c7b77c72c239182caf1d061d6fff6ffbbfe8d3b8f3475c2fe352e563aa60290c666b2e627761e32155e62f048b52ef2f39c13ac229f393c67811749467396ecd09f42d32a4eb419117d0451056ac18fac957c52b0cca67568e2d97e5a3fd829a77f914a1ad403c5909fd510a313033422ea5db71eaf43d483238612a54cb1ecfe55259b1de5579e67c6505df7d674d34a737edf721ea69d15b567bc2195ec67e172f3cb8d6842ca88c29138cc33e9351dbc1e4973a82e1cf428c1c763bb8f3eb57770f914a"; - - Key privateKey = Key.decodeKey(Common.otherPrivateKey); - Key publicKey = Key.decodeKey(Common.myPublicKey); - - ThreemaMessage message = CryptTool.decryptMessage(DataUtils.hexStringToByteArray(box), privateKey.key, - publicKey.key, DataUtils.hexStringToByteArray(nonce)); - - Assert.assertNotNull(message); - Assert.assertTrue("message is not a instance of text message", message instanceof TextMessage); - Assert.assertEquals(((TextMessage) message).getText(), "Dies ist eine Testnachricht. äöü"); - } - - @Test - public void testEncrypt() throws Exception { - String text = "Dies ist eine Testnachricht. äöü"; - - Key privateKey = Key.decodeKey(Common.myPrivateKey); - Key publicKey = Key.decodeKey(Common.otherPublicKey); - - EncryptResult res = CryptTool.encryptTextMessage(text, privateKey.key, publicKey.key); - Assert.assertNotNull(res); - Assert.assertNotNull(res.getNonce()); - Assert.assertNotNull(res.getResult()); - Assert.assertFalse(Common.isEmpty(res.getNonce())); - Assert.assertFalse(Common.isEmpty(res.getResult())); - } - - @Test - public void testDerivePublicKey() throws Exception { - Key privateKey = Key.decodeKey(Common.myPrivateKey); - Key publicKey = Key.decodeKey(Common.myPublicKey); - byte[] derivedPublicKey = CryptTool.derivePublicKey(privateKey.key); - Assert.assertNotNull("derived public key", derivedPublicKey); - Assert.assertEquals(derivedPublicKey, publicKey.key); - } -} \ No newline at end of file diff --git a/source/src/test/java/ch/threema/apitool/KeyTest.java b/source/src/test/java/ch/threema/apitool/KeyTest.java deleted file mode 100644 index 104188b..0000000 --- a/source/src/test/java/ch/threema/apitool/KeyTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -import ch.threema.apitool.exceptions.InvalidKeyException; -import org.junit.Test; - -public class KeyTest { - - @Test - public void testDecodeWrongKey() { - try { - Key.decodeKey("imnotarealkey"); - } catch (InvalidKeyException e) { - return; - } - Assert.assertFalse("could parse invalid key", true); - } - - @Test - public void testDecodeKeyPrivate() throws Exception { - Key key = Key.decodeKey("private:1234567890123456789012345678901234567890123456789012345678901234"); - Assert.assertNotNull("key instance", key); - - Assert.assertEquals(key.type, Key.KeyType.PRIVATE); - Assert.assertEquals(key.key, DataUtils.hexStringToByteArray("1234567890123456789012345678901234567890123456789012345678901234")); - } - @Test - public void testDecodeKeyPublic() throws Exception { - Key key = Key.decodeKey("public:1234567890123456789012345678901234567890123456789012345678901234"); - Assert.assertNotNull("key instance", key); - - Assert.assertEquals(key.type, Key.KeyType.PUBLIC); - Assert.assertEquals(key.key, DataUtils.hexStringToByteArray("1234567890123456789012345678901234567890123456789012345678901234")); - } - - @Test - public void testEncodePrivate() throws Exception { - byte[] keyAsByte = DataUtils.hexStringToByteArray(Common.myPrivateKeyExtract); - - Key key = new Key(Key.KeyType.PRIVATE, keyAsByte); - Assert.assertNotNull("key instance", key); - - Assert.assertEquals(Key.KeyType.PRIVATE, key.type); - Assert.assertEquals(key.key, keyAsByte); - - Assert.assertEquals(key.encode(), Common.myPrivateKey); - } -} \ No newline at end of file