/* * Copyright (C) 2014-2015 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.operations; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.net.Uri; import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; /** * This is a high-level operation, which encapsulates one or more sign/encrypt * operations, using URIs or byte arrays as input and output. * * This operation is fail-fast: If any sign/encrypt sub-operation fails or returns * a pending result, it will terminate. */ public class SignEncryptOperation extends BaseOperation { public SignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) { super(context, providerHelper, progressable, cancelled); } @NonNull public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) { OperationLog log = new OperationLog(); log.add(LogType.MSG_SE, 0); ArrayDeque inputUris = new ArrayDeque<>(input.getInputUris()); ArrayDeque outputUris = new ArrayDeque<>(input.getOutputUris()); byte[] inputBytes = input.getBytes(); byte[] outputBytes = null; int total = inputBytes != null ? 1 : inputUris.size(), count = 0; ArrayList results = new ArrayList<>(); SecurityTokenSignOperationsBuilder pendingInputBuilder = null; // if signing subkey has not explicitly been set, get first usable subkey capable of signing if (input.getSignatureMasterKeyId() != Constants.key.none && input.getSignatureSubKeyId() == null) { try { long signKeyId = mProviderHelper.getCachedPublicKeyRing( input.getSignatureMasterKeyId()).getSecretSignId(); input.setSignatureSubKeyId(signKeyId); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "Key not found", e); return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); } } do { if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, 0); return new SignEncryptResult(SignEncryptResult.RESULT_CANCELLED, log, results); } InputData inputData; { if (inputBytes != null) { log.add(LogType.MSG_SE_INPUT_BYTES, 1); InputStream is = new ByteArrayInputStream(inputBytes); inputData = new InputData(is, inputBytes.length); inputBytes = null; } else { if (inputUris.isEmpty()) { log.add(LogType.MSG_SE_ERROR_NO_INPUT, 1); return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); } log.add(LogType.MSG_SE_INPUT_URI, 1); Uri uri = inputUris.removeFirst(); try { InputStream is = FileHelper.openInputStreamSafe(mContext.getContentResolver(), uri); long fileSize = FileHelper.getFileSize(mContext, uri, 0); String filename = FileHelper.getFilename(mContext, uri); inputData = new InputData(is, fileSize, filename); } catch (FileNotFoundException e) { log.add(LogType.MSG_SE_ERROR_INPUT_URI_NOT_FOUND, 1); return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); } } } OutputStream outStream; { if (!outputUris.isEmpty()) { try { Uri outputUri = outputUris.removeFirst(); outStream = mContext.getContentResolver().openOutputStream(outputUri); } catch (FileNotFoundException e) { log.add(LogType.MSG_SE_ERROR_OUTPUT_URI_NOT_FOUND, 1); return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); } } else { if (outputBytes != null) { log.add(LogType.MSG_SE_ERROR_TOO_MANY_INPUTS, 1); return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); } outStream = new ByteArrayOutputStream(); } } PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper, new ProgressScaler(mProgressable, 100 * count / total, 100 * ++count / total, 100), mCancelled); PgpSignEncryptResult result = op.execute(input, cryptoInput, inputData, outStream); results.add(result); log.add(result, 2); if (result.isPending()) { RequiredInputParcel requiredInput = result.getRequiredInputParcel(); // Passphrase returns immediately, nfc are aggregated if (requiredInput.mType == RequiredInputType.PASSPHRASE) { return new SignEncryptResult(log, requiredInput, results, cryptoInput); } if (pendingInputBuilder == null) { pendingInputBuilder = new SecurityTokenSignOperationsBuilder(requiredInput.mSignatureTime, input.getSignatureMasterKeyId(), input.getSignatureSubKeyId()); } pendingInputBuilder.addAll(requiredInput); } else if (!result.success()) { return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); } if (outStream instanceof ByteArrayOutputStream) { outputBytes = ((ByteArrayOutputStream) outStream).toByteArray(); } } while (!inputUris.isEmpty()); if (pendingInputBuilder != null && !pendingInputBuilder.isEmpty()) { return new SignEncryptResult(log, pendingInputBuilder.build(), results, cryptoInput); } if (!outputUris.isEmpty()) { throw new AssertionError("Got outputs left but no inputs. This is a programming error, please report!"); } log.add(LogType.MSG_SE_SUCCESS, 1); return new SignEncryptResult(SignEncryptResult.RESULT_OK, log, results, outputBytes); } }