Init libkeychain and move some utils
This commit is contained in:
2
libkeychain/src/main/AndroidManifest.xml
Normal file
2
libkeychain/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="org.sufficientlysecure.libkeychain" />
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2016 Vincent Breitmoser
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.bouncycastle.openpgp.jcajce;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPMarker;
|
||||
|
||||
/** This class wraps the regular PGPObjectFactory, changing its behavior to
|
||||
* ignore all PGPMarker packets it encounters while reading. These packets
|
||||
* carry no semantics of their own, and should be ignored according to
|
||||
* RFC 4880.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc4880#section-5.8
|
||||
* @see org.bouncycastle.openpgp.PGPMarker
|
||||
*
|
||||
*/
|
||||
public class JcaSkipMarkerPGPObjectFactory extends JcaPGPObjectFactory {
|
||||
|
||||
public JcaSkipMarkerPGPObjectFactory(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object nextObject() throws IOException {
|
||||
Object o = super.nextObject();
|
||||
while (o instanceof PGPMarker) {
|
||||
o = super.nextObject();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.bouncycastle.openpgp.operator.jcajce;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
|
||||
public class CachingDataDecryptorFactory implements PublicKeyDataDecryptorFactory
|
||||
{
|
||||
private final PublicKeyDataDecryptorFactory mWrappedDecryptor;
|
||||
private final Map<ByteBuffer, byte[]> mSessionKeyCache;
|
||||
|
||||
private OperatorHelper mOperatorHelper;
|
||||
|
||||
public CachingDataDecryptorFactory(String providerName,
|
||||
final Map<ByteBuffer,byte[]> sessionKeyCache)
|
||||
{
|
||||
mWrappedDecryptor = null;
|
||||
mSessionKeyCache = sessionKeyCache;
|
||||
|
||||
mOperatorHelper = new OperatorHelper(new NamedJcaJceHelper(providerName));
|
||||
}
|
||||
|
||||
public CachingDataDecryptorFactory(PublicKeyDataDecryptorFactory wrapped,
|
||||
final Map<ByteBuffer,byte[]> sessionKeyCache)
|
||||
{
|
||||
mWrappedDecryptor = wrapped;
|
||||
mSessionKeyCache = sessionKeyCache;
|
||||
|
||||
}
|
||||
|
||||
public boolean hasCachedSessionData(PGPPublicKeyEncryptedData encData) throws PGPException {
|
||||
ByteBuffer bi = ByteBuffer.wrap(encData.getSessionKey()[0]);
|
||||
return mSessionKeyCache.containsKey(bi);
|
||||
}
|
||||
|
||||
public Map<ByteBuffer, byte[]> getCachedSessionKeys() {
|
||||
return mSessionKeyCache;
|
||||
}
|
||||
|
||||
public boolean canDecrypt() {
|
||||
return mWrappedDecryptor != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException {
|
||||
ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]); // encoded MPI
|
||||
if (mSessionKeyCache.containsKey(bi)) {
|
||||
return mSessionKeyCache.get(bi);
|
||||
}
|
||||
|
||||
if (mWrappedDecryptor == null) {
|
||||
throw new IllegalStateException("tried to decrypt without wrapped decryptor, this is a bug!");
|
||||
}
|
||||
|
||||
byte[] sessionData = mWrappedDecryptor.recoverSessionData(keyAlgorithm, secKeyData);
|
||||
mSessionKeyCache.put(bi, sessionData);
|
||||
return sessionData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
|
||||
throws PGPException {
|
||||
if (mWrappedDecryptor != null) {
|
||||
return mWrappedDecryptor.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
|
||||
}
|
||||
return mOperatorHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
|
||||
* Copyright (c) 2000-2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.bouncycastle.openpgp.operator.jcajce;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSigner;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.Provider;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* This class is based on JcaPGPContentSignerBuilder.
|
||||
*
|
||||
* Instead of using a Signature object based on a privateKey, this class only calculates the digest
|
||||
* of the output stream and gives the result back using a RuntimeException.
|
||||
*/
|
||||
public class NfcSyncPGPContentSignerBuilder
|
||||
implements PGPContentSignerBuilder
|
||||
{
|
||||
private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
|
||||
private int hashAlgorithm;
|
||||
private int keyAlgorithm;
|
||||
private long keyID;
|
||||
|
||||
private Map signedHashes;
|
||||
|
||||
public static class NfcInteractionNeeded extends RuntimeException
|
||||
{
|
||||
public byte[] hashToSign;
|
||||
public int hashAlgo;
|
||||
|
||||
public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo)
|
||||
{
|
||||
super("NFC interaction required!");
|
||||
this.hashToSign = hashToSign;
|
||||
this.hashAlgo = hashAlgo;
|
||||
}
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, Map signedHashes)
|
||||
{
|
||||
this.keyAlgorithm = keyAlgorithm;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.keyID = keyID;
|
||||
this.signedHashes = signedHashes;
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder setProvider(Provider provider)
|
||||
{
|
||||
digestCalculatorProviderBuilder.setProvider(provider);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder setProvider(String providerName)
|
||||
{
|
||||
digestCalculatorProviderBuilder.setProvider(providerName);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder setDigestProvider(Provider provider)
|
||||
{
|
||||
digestCalculatorProviderBuilder.setProvider(provider);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder setDigestProvider(String providerName)
|
||||
{
|
||||
digestCalculatorProviderBuilder.setProvider(providerName);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public PGPContentSigner build(final int signatureType, PGPPrivateKey privateKey)
|
||||
throws PGPException {
|
||||
// NOTE: privateKey is null in this case!
|
||||
return build(signatureType, keyID);
|
||||
}
|
||||
|
||||
public PGPContentSigner build(final int signatureType, final long keyID)
|
||||
throws PGPException
|
||||
{
|
||||
final PGPDigestCalculator digestCalculator = digestCalculatorProviderBuilder.build().get(hashAlgorithm);
|
||||
|
||||
return new PGPContentSigner()
|
||||
{
|
||||
public int getType()
|
||||
{
|
||||
return signatureType;
|
||||
}
|
||||
|
||||
public int getHashAlgorithm()
|
||||
{
|
||||
return hashAlgorithm;
|
||||
}
|
||||
|
||||
public int getKeyAlgorithm()
|
||||
{
|
||||
return keyAlgorithm;
|
||||
}
|
||||
|
||||
public long getKeyID()
|
||||
{
|
||||
return keyID;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return digestCalculator.getOutputStream();
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
byte[] digest = digestCalculator.getDigest();
|
||||
ByteBuffer buf = ByteBuffer.wrap(digest);
|
||||
if (signedHashes.containsKey(buf)) {
|
||||
return (byte[]) signedHashes.get(buf);
|
||||
}
|
||||
// catch this when signatureGenerator.generate() is executed and divert digest to card,
|
||||
// when doing the operation again reuse creationTimestamp (this will be hashed)
|
||||
throw new NfcInteractionNeeded(digest, getHashAlgorithm());
|
||||
}
|
||||
|
||||
public byte[] getDigest()
|
||||
{
|
||||
return digestCalculator.getDigest();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2016 Vincent Breitmoser
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.bouncycastle.openpgp.operator.jcajce;
|
||||
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Provider;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
|
||||
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
|
||||
import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
|
||||
|
||||
/** This is a builder for a special PBESecretKeyDecryptor which is parametrized by a
|
||||
* fixed session key, which is used in place of the one obtained from a passphrase.
|
||||
*/
|
||||
public class SessionKeySecretKeyDecryptorBuilder
|
||||
{
|
||||
private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
|
||||
private PGPDigestCalculatorProvider calculatorProvider;
|
||||
|
||||
private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder;
|
||||
|
||||
public SessionKeySecretKeyDecryptorBuilder()
|
||||
{
|
||||
this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
|
||||
}
|
||||
|
||||
public SessionKeySecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider)
|
||||
{
|
||||
this.calculatorProvider = calculatorProvider;
|
||||
}
|
||||
|
||||
public SessionKeySecretKeyDecryptorBuilder setProvider(Provider provider)
|
||||
{
|
||||
this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
|
||||
|
||||
if (calculatorProviderBuilder != null)
|
||||
{
|
||||
calculatorProviderBuilder.setProvider(provider);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public SessionKeySecretKeyDecryptorBuilder setProvider(String providerName)
|
||||
{
|
||||
this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
|
||||
|
||||
if (calculatorProviderBuilder != null)
|
||||
{
|
||||
calculatorProviderBuilder.setProvider(providerName);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public PBESecretKeyDecryptor build(final byte[] sessionKey)
|
||||
throws PGPException
|
||||
{
|
||||
if (calculatorProvider == null)
|
||||
{
|
||||
calculatorProvider = calculatorProviderBuilder.build();
|
||||
}
|
||||
|
||||
return new PBESecretKeyDecryptor(null, calculatorProvider)
|
||||
{
|
||||
@Override
|
||||
public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k) throws PGPException {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
|
||||
throws PGPException
|
||||
{
|
||||
try
|
||||
{
|
||||
Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding");
|
||||
|
||||
c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv));
|
||||
|
||||
return c.doFinal(keyData, keyOff, keyLen);
|
||||
}
|
||||
catch (IllegalBlockSizeException e)
|
||||
{
|
||||
throw new PGPException("illegal block size: " + e.getMessage(), e);
|
||||
}
|
||||
catch (BadPaddingException e)
|
||||
{
|
||||
throw new PGPException("bad padding: " + e.getMessage(), e);
|
||||
}
|
||||
catch (InvalidAlgorithmParameterException e)
|
||||
{
|
||||
throw new PGPException("invalid parameter: " + e.getMessage(), e);
|
||||
}
|
||||
catch (InvalidKeyException e)
|
||||
{
|
||||
throw new PGPException("invalid key: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.sufficientlysecure.libkeychain.BuildConfig;
|
||||
|
||||
public final class LibConstants {
|
||||
|
||||
public static final boolean DEBUG = BuildConfig.DEBUG;
|
||||
|
||||
public static final String TAG = DEBUG ? "Keychain D" : "Keychain";
|
||||
|
||||
public static final String BOUNCY_CASTLE_PROVIDER_NAME = BouncyCastleProvider.PROVIDER_NAME;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
|
||||
import android.content.ClipDescription;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/** This class can be used to guess whether a stream of data is encoded in a given
|
||||
* charset or not.
|
||||
*
|
||||
* An object of this class must be initialized with a byte[] buffer, which should
|
||||
* be filled with data, then processed with {@link #readBytesFromBuffer}. This can
|
||||
* be done any number of times. Once all data has been read, a final status can be
|
||||
* read using the getter methods.
|
||||
*/
|
||||
public class CharsetVerifier {
|
||||
|
||||
private final ByteBuffer bufWrap;
|
||||
private final CharBuffer dummyOutput;
|
||||
|
||||
private final CharsetDecoder charsetDecoder;
|
||||
|
||||
private boolean isFinished;
|
||||
private boolean isFaulty;
|
||||
private boolean isGuessed;
|
||||
private boolean isPossibleTextMimeType;
|
||||
private boolean isTextMimeType;
|
||||
private String charset;
|
||||
private String mimeType;
|
||||
|
||||
public CharsetVerifier(@NonNull byte[] buf, @NonNull String mimeType, @Nullable String charset) {
|
||||
|
||||
this.mimeType = mimeType;
|
||||
isTextMimeType = ClipDescription.compareMimeTypes(mimeType, "text/*");
|
||||
isPossibleTextMimeType = isTextMimeType
|
||||
|| ClipDescription.compareMimeTypes(mimeType, "application/octet-stream")
|
||||
|| ClipDescription.compareMimeTypes(mimeType, "application/x-download");
|
||||
if (!isPossibleTextMimeType) {
|
||||
charsetDecoder = null;
|
||||
bufWrap = null;
|
||||
dummyOutput = null;
|
||||
return;
|
||||
}
|
||||
|
||||
bufWrap = ByteBuffer.wrap(buf);
|
||||
dummyOutput = CharBuffer.allocate(buf.length);
|
||||
|
||||
// the charset defaults to us-ascii, but we want to default to utf-8
|
||||
if (charset == null || "us-ascii".equals(charset)) {
|
||||
charset = "utf-8";
|
||||
isGuessed = true;
|
||||
} else {
|
||||
isGuessed = false;
|
||||
}
|
||||
this.charset = charset;
|
||||
|
||||
charsetDecoder = Charset.forName(charset).newDecoder();
|
||||
charsetDecoder.onMalformedInput(CodingErrorAction.REPORT);
|
||||
charsetDecoder.onUnmappableCharacter(CodingErrorAction.REPORT);
|
||||
charsetDecoder.reset();
|
||||
}
|
||||
|
||||
public void readBytesFromBuffer(int pos, int len) {
|
||||
if (isFinished) {
|
||||
throw new IllegalStateException("cannot write again after reading charset status!");
|
||||
}
|
||||
if (isFaulty || bufWrap == null) {
|
||||
return;
|
||||
}
|
||||
bufWrap.rewind();
|
||||
bufWrap.position(pos);
|
||||
bufWrap.limit(len);
|
||||
dummyOutput.rewind();
|
||||
CoderResult result = charsetDecoder.decode(bufWrap, dummyOutput, false);
|
||||
if (result.isError()) {
|
||||
isFaulty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void finishIfNecessary() {
|
||||
if (isFinished || isFaulty || bufWrap == null) {
|
||||
return;
|
||||
}
|
||||
isFinished = true;
|
||||
bufWrap.rewind();
|
||||
bufWrap.limit(0);
|
||||
dummyOutput.rewind();
|
||||
CoderResult result = charsetDecoder.decode(bufWrap, dummyOutput, true);
|
||||
if (result.isError()) {
|
||||
isFaulty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public String getGuessedMimeType() {
|
||||
if (isTextMimeType) {
|
||||
return mimeType;
|
||||
}
|
||||
if (isProbablyText()) {
|
||||
return "text/plain";
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public boolean isCharsetFaulty() {
|
||||
finishIfNecessary();
|
||||
return isFaulty;
|
||||
}
|
||||
|
||||
public boolean isCharsetGuessed() {
|
||||
finishIfNecessary();
|
||||
return isGuessed;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
finishIfNecessary();
|
||||
if (!isPossibleTextMimeType || (isGuessed && isFaulty)) {
|
||||
return null;
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
public String getMaybeFaultyCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
/** Returns true if the data which was read is definitely binary.
|
||||
*
|
||||
* This can happen when either the supplied mimeType indicated a non-ambiguous
|
||||
* binary data type, or if we guessed a charset but got errors while decoding.
|
||||
*/
|
||||
public boolean isDefinitelyBinary() {
|
||||
finishIfNecessary();
|
||||
return !isTextMimeType && (!isPossibleTextMimeType || (isGuessed && isFaulty));
|
||||
}
|
||||
|
||||
/** Returns true iff the data which was read is probably (or
|
||||
* definitely) text.
|
||||
*
|
||||
* The corner case where isDefinitelyBinary returns false but isProbablyText
|
||||
* returns true is where the charset was provided by the data (so is not
|
||||
* guessed) but is still faulty.
|
||||
*/
|
||||
public boolean isProbablyText() {
|
||||
finishIfNecessary();
|
||||
return isTextMimeType || isPossibleTextMimeType && (!isGuessed || !isFaulty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Guava Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
|
||||
public final class CountingOutputStream extends FilterOutputStream {
|
||||
|
||||
private long count;
|
||||
|
||||
/**
|
||||
* Wraps another output stream, counting the number of bytes written.
|
||||
*
|
||||
* @param out
|
||||
* the output stream to be wrapped
|
||||
*/
|
||||
public CountingOutputStream(@NonNull OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes written.
|
||||
*/
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
out.write(b, off, len);
|
||||
count += len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
out.write(b);
|
||||
count++;
|
||||
}
|
||||
|
||||
// Overriding close() because FilterOutputStream's close() method pre-JDK8 has bad behavior:
|
||||
// it silently ignores any exception thrown by flush(). Instead, just close the delegate stream.
|
||||
// It should flush itself if necessary.
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Wrapper to include size besides an InputStream
|
||||
*/
|
||||
public class InputData {
|
||||
public static final int UNKNOWN_FILESIZE = -1;
|
||||
|
||||
private PositionAwareInputStream mInputStream;
|
||||
private long mSize;
|
||||
String mOriginalFilename;
|
||||
|
||||
public InputData(InputStream inputStream, long size, String originalFilename) {
|
||||
mInputStream = new PositionAwareInputStream(inputStream);
|
||||
mSize = size;
|
||||
mOriginalFilename = originalFilename;
|
||||
}
|
||||
|
||||
public InputData(InputStream inputStream, long size) {
|
||||
mInputStream = new PositionAwareInputStream(inputStream);
|
||||
mSize = size;
|
||||
mOriginalFilename = "";
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return mInputStream;
|
||||
}
|
||||
|
||||
public String getOriginalFilename () {
|
||||
return mOriginalFilename;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
public long getStreamPosition() {
|
||||
return mInputStream.position();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class IterableIterator<T> implements Iterable<T> {
|
||||
private Iterator<T> mIter;
|
||||
|
||||
public IterableIterator(Iterator<T> iter, boolean failsafe) {
|
||||
mIter = iter;
|
||||
if (failsafe && mIter == null) {
|
||||
// is there a better way?
|
||||
mIter = new ArrayList<T>().iterator();
|
||||
}
|
||||
}
|
||||
public IterableIterator(Iterator<T> iter) {
|
||||
this(iter, false);
|
||||
}
|
||||
|
||||
public Iterator<T> iterator() {
|
||||
return mIter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* An extended iterator interface, which knows the total number of its entries beforehand.
|
||||
*/
|
||||
public interface IteratorWithSize<E> extends Iterator<E> {
|
||||
|
||||
/**
|
||||
* Returns the total number of entries in this iterator.
|
||||
*
|
||||
* @return the number of entries in this iterator.
|
||||
*/
|
||||
int getSize();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.sufficientlysecure.keychain.LibConstants;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Wraps Android Logging to enable or disable debug output using Constants
|
||||
*/
|
||||
public final class LibLog {
|
||||
|
||||
public static void v(String tag, String msg) {
|
||||
if (LibConstants.DEBUG) {
|
||||
android.util.Log.v(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void v(String tag, String msg, Throwable tr) {
|
||||
if (LibConstants.DEBUG) {
|
||||
android.util.Log.v(tag, msg, tr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(String tag, String msg) {
|
||||
if (LibConstants.DEBUG) {
|
||||
android.util.Log.d(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(String tag, String msg, Throwable tr) {
|
||||
if (LibConstants.DEBUG) {
|
||||
android.util.Log.d(tag, msg, tr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dEscaped(String tag, String msg) {
|
||||
if (LibConstants.DEBUG) {
|
||||
android.util.Log.d(tag, removeUnicodeAndEscapeChars(msg));
|
||||
}
|
||||
}
|
||||
|
||||
public static void dEscaped(String tag, String msg, Throwable tr) {
|
||||
if (LibConstants.DEBUG) {
|
||||
android.util.Log.d(tag, removeUnicodeAndEscapeChars(msg), tr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void i(String tag, String msg) {
|
||||
if (LibConstants.DEBUG) {
|
||||
android.util.Log.i(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void i(String tag, String msg, Throwable tr) {
|
||||
if (LibConstants.DEBUG) {
|
||||
android.util.Log.i(tag, msg, tr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void w(String tag, String msg) {
|
||||
android.util.Log.w(tag, msg);
|
||||
}
|
||||
|
||||
public static void w(String tag, String msg, Throwable tr) {
|
||||
android.util.Log.w(tag, msg, tr);
|
||||
}
|
||||
|
||||
public static void w(String tag, Throwable tr) {
|
||||
android.util.Log.w(tag, tr);
|
||||
}
|
||||
|
||||
public static void e(String tag, String msg) {
|
||||
android.util.Log.e(tag, msg);
|
||||
}
|
||||
|
||||
public static void e(String tag, String msg, Throwable tr) {
|
||||
android.util.Log.e(tag, msg, tr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logs bundle content to debug for inspecting the content
|
||||
*
|
||||
* @param bundle
|
||||
* @param bundleName
|
||||
*/
|
||||
public static void logDebugBundle(Bundle bundle, String bundleName) {
|
||||
if (LibConstants.DEBUG) {
|
||||
if (bundle != null) {
|
||||
Set<String> ks = bundle.keySet();
|
||||
Iterator<String> iterator = ks.iterator();
|
||||
|
||||
LibLog.d(LibConstants.TAG, "Bundle " + bundleName + ":");
|
||||
LibLog.d(LibConstants.TAG, "------------------------------");
|
||||
while (iterator.hasNext()) {
|
||||
String key = iterator.next();
|
||||
Object value = bundle.get(key);
|
||||
|
||||
if (value != null) {
|
||||
LibLog.d(LibConstants.TAG, key + " : " + value.toString());
|
||||
} else {
|
||||
LibLog.d(LibConstants.TAG, key + " : null");
|
||||
}
|
||||
}
|
||||
LibLog.d(LibConstants.TAG, "------------------------------");
|
||||
} else {
|
||||
LibLog.d(LibConstants.TAG, "Bundle " + bundleName + ": null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String removeUnicodeAndEscapeChars(String input) {
|
||||
StringBuilder buffer = new StringBuilder(input.length());
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
if ((int) input.charAt(i) > 256) {
|
||||
buffer.append("\\u").append(Integer.toHexString((int) input.charAt(i)));
|
||||
} else {
|
||||
if (input.charAt(i) == '\n') {
|
||||
buffer.append("\\n");
|
||||
} else if (input.charAt(i) == '\t') {
|
||||
buffer.append("\\t");
|
||||
} else if (input.charAt(i) == '\r') {
|
||||
buffer.append("\\r");
|
||||
} else if (input.charAt(i) == '\b') {
|
||||
buffer.append("\\b");
|
||||
} else if (input.charAt(i) == '\f') {
|
||||
buffer.append("\\f");
|
||||
} else if (input.charAt(i) == '\'') {
|
||||
buffer.append("\\'");
|
||||
} else if (input.charAt(i) == '\"') {
|
||||
buffer.append("\\");
|
||||
} else if (input.charAt(i) == '\\') {
|
||||
buffer.append("\\\\");
|
||||
} else {
|
||||
buffer.append(input.charAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class PositionAwareInputStream extends InputStream {
|
||||
private InputStream mStream;
|
||||
private long mPosition;
|
||||
|
||||
public PositionAwareInputStream(InputStream in) {
|
||||
mStream = in;
|
||||
mPosition = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int ch = mStream.read();
|
||||
++mPosition;
|
||||
return ch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return mStream.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mStream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
int result = mStream.read(b);
|
||||
mPosition += result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int offset, int length) throws IOException {
|
||||
int result = mStream.read(b, offset, length);
|
||||
mPosition += result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
mStream.reset();
|
||||
mPosition = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
long result = mStream.skip(n);
|
||||
mPosition += result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public long position() {
|
||||
return mPosition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
import org.sufficientlysecure.keychain.LibConstants;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
|
||||
public class Utf8Util {
|
||||
|
||||
public static boolean isValidUTF8(byte[] input) {
|
||||
CharsetDecoder cs = Charset.forName("UTF-8").newDecoder();
|
||||
|
||||
try {
|
||||
cs.decode(ByteBuffer.wrap(input));
|
||||
return true;
|
||||
} catch (CharacterCodingException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String fromUTF8ByteArrayReplaceBadEncoding(byte[] input) {
|
||||
final CharsetDecoder charsetDecoder = Charset.forName("UTF-8").newDecoder();
|
||||
charsetDecoder.onMalformedInput(CodingErrorAction.REPLACE);
|
||||
charsetDecoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
||||
|
||||
try {
|
||||
return charsetDecoder.decode(ByteBuffer.wrap(input)).toString();
|
||||
} catch (CharacterCodingException e) {
|
||||
LibLog.e(LibConstants.TAG, "Decoding failed!", e);
|
||||
return charsetDecoder.replacement();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user