How to make SSL peer_verify work on Android? - android-ndk

I've successfully built libcurl-7.36.0 with openssl-1.0.1h on Android. I ran a sample code to test HTTPS connection. The SSL_VERIFYPEER is enabled by default. The certificates path on Android is /system/etc/security/cacerts, so I set CURLOPT_CAPATH to /system/etc/security/cacerts.
ls -l /system/etc/security/cacerts
-rw-r--r-- root root 4767 2012-09-22 11:57 00673b5b.0
-rw-r--r-- root root 4573 2012-09-22 11:57 03e16f6c.0
-rw-r--r-- root root 5292 2012-09-22 11:57 08aef7bb.0
......
Here is a snippet of my codes..
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "https://www.google.com:443");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); // default
curl_easy_setopt(curl, CURLOPT_CAPATH, "/system/etc/security/cacerts");
curl_easy_perform(curl);
Curl always returns an error:
== Info: SSL certificate problem: unable to get local issuer certificate
== Info: Closing connection 0
curl_easy_perform() failed: Peer certificate cannot be authenticated with given CA certificates
It's working if I download the CA bundle file ca-bundle.crt from http://curl.haxx.se/docs/caextract.html and curl_easy_setopt(curl, CURLOPT_CAINFO, "path:/ca-bundle.crt").
Here is my question: Is there any way to make SSL peer verification work by reading the certificate from /system/etc/security/cacerts without manually downloading the CA bundle file and specifying CURLOPT_CAINFO?

If using libcurl in an android app, CURLOPT_SSL_VERIFYPEER will fail and hence prevent CURL from sending data if if there is no CA bundle . One way to overcome this is to turn off this option which is very very very bad.
We must provide our own CA bundle and provide the absolute path of the CA bundle file using CURLOPT_CAINFO option.
The "cacert.pem" file from ​http://curl.haxx.se/docs/caextract.html can be placed in resources or assets but I prefer assets directory.
CURL expects absolute path and we cant give absolute path of assets folder because a packaged android APK file is like a zipped folder hence we need to copy the PEM file from assets to internal storage or external storage but I prefer internal storage since it private to the app and provide the absolute path of the internal storage directory in CAINFO. For example if app name is com.example.androidtest then CAINFO path will be "/data/data/com.example.androidtest/cacert.pem" .
Sample implementation of CURL using TLS1.2 ,openSSL 1.01p,curl version 7.40.0 ,cacert.pem bundle with verify peer ,verify hostname option is shown in https://github.com/vyshas/CURL-Android-with-verify-peer-
Important parts from the above link is shown below:
JAVA Side
public native void setDir(String caCertDir);
setDir(saveCertPemFile());
private String saveCertPemFile()
{
Context context=getApplicationContext();
String assetFileName="cacert.pem";
if(context==null || !FileExistInAssets(assetFileName,context))
{
Log.i("TestActivity", "Context is null or asset file doesnt exist");
return null;
}
//destination path is data/data/packagename
String destPath=getApplicationContext().getApplicationInfo().dataDir;
String CertFilePath =destPath + "/" +assetFileName;
File file = new File(CertFilePath);
if(file.exists())
{
//delete file
file.delete();
}
//copy to internal storage
if(CopyAssets(context,assetFileName,CertFilePath)==1) return CertFilePath;
return CertFilePath=null;
}
private int CopyAssets(Context context,String assetFileName, String toPath)
{
AssetManager assetManager = context.getAssets();
InputStream in = null;
OutputStream out = null;
try {
in = assetManager.open(assetFileName);
new File(toPath).createNewFile();
out = new FileOutputStream(toPath);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
in.close();
in = null;
out.flush();
out.close();
out = null;
return 1;
} catch(Exception e) {
Log.e("tag", "CopyAssets"+e.getMessage());
}
return 0;
}
private boolean FileExistInAssets(String fileName,Context context)
{
try {
return Arrays.asList(context.getResources().getAssets().list("")).contains(fileName);
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e("tag", "FileExistInAssets"+e.getMessage());
}
return false;
}
JNI SIDE
JNIEXPORT void JNICALL Java_com_example_androidtest_TestActivity_setDir(JNIEnv* env, jobject obj, jstring caCertDir)
{
if(!caCertDir) return;
const char* caCertDir_c = env->GetStringUTFChars(caCertDir, NULL);
if (!caCertDir_c) return ;
const jsize len = env->GetStringUTFLength(caCertDir);
LOGI( "CaCertDir: %s", caCertDir_c );
std::string caCert(caCertDir_c,len);
caCertPtr=caCert;
LOGI( "CaCertDirptr in std string: %s", caCertPtr.c_str());
env->ReleaseStringUTFChars(caCertDir, caCertDir_c);
}
CURL code
CURL* curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
/* curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, TRUE);*/
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curlCallback);
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, downloadObject);
curl_easy_setopt(curl,CURLOPT_CAINFO,caCertPtr.c_str());
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_version_info_data * vinfo = curl_version_info( CURLVERSION_NOW );
if( vinfo->features & CURL_VERSION_SSL )
// SSL support enabled
LOGI("SSL support enabled");
else
{// No SSL
LOGI("NO SSL");
}
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK){
LOGI("CURL failed with error code %d", res);
}
LOGI("CURL download is OK, result:%d", res);
curl_easy_cleanup(curl);
return res == CURLE_OK;

OpenSSL 0.9.x used MD5 filename hash. OpenSSL 1.0.x used SHA-1 for the filename hash. Android is using MD5 hash. Why old hash?
I tried libcurl-7.36.0 with openssl-0.9.8zb. It's working on Android with CURLOPT_SSL_VERIFYPEER enabled.

The problem is not on Curl, but on openSSL.
openssl 1.1.1x at openssl-1.1.1x/crypto/x509/by_dir.c:
function get_cert_by_subject(), it using X509_NAME_hash() that not compatible with android.
try to modify the by_dir.c at openssl source:
#if defined(__ANDROID__)
h = X509_NAME_hash_old(name);
#else
h = X509_NAME_hash(name);
#endif
it's should be solve the problem.
patch:
--- a/openssl-1.1.1k/crypto/x509/by_dir.c
+++ b/openssl-1.1.1k/crypto/x509/by_dir.c
## -247,7 +247,11 ## static int get_cert_by_subject(X509_LOOKUP *xl,
ctx = (BY_DIR *)xl->method_data;
+#if defined(__ANDROID__)
+ h = X509_NAME_hash_old(name);
+#else
h = X509_NAME_hash(name);
+#endif
for (i = 0; i < sk_BY_DIR_ENTRY_num(ctx->dirs); i++) {
BY_DIR_ENTRY *ent;
int idx;

EDIT: my previous answer was wrong.
CURLOPT_CAPATH should point to a directory prepared for OpenSSL with the c_hash tool. I don't know if that's the same format that Android provides.
I found this description on how to import new certs to a recent Android, and it seems to indicate a slightly different format of the files in that directory than what c_hash makes...

I got this to work on Android by recompiling libcurl and configuring the default search path for certificates. This can be done by passing the option:
--with-ca-path=/system/etc/security/cacerts to ./configure
or
-DCURL_CA_PATH=/system/etc/security/cacerts to cmake

Related

C#: WPD - Downloading a Picture with meta tags

I am running the Portable Device API to automatically get Photos from a connected Smart Phone. I have it all transferring correctly. The code that i use is that Standard DownloadFile() routine:
public PortableDownloadInfo DownloadFile(PortableDeviceFile file, string saveToPath)
{
IPortableDeviceContent content;
_device.Content(out content);
IPortableDeviceResources resources;
content.Transfer(out resources);
PortableDeviceApiLib.IStream wpdStream;
uint optimalTransferSize = 0;
var property = new _tagpropertykey
{
fmtid = new Guid(0xE81E79BE, 0x34F0, 0x41BF, 0xB5, 0x3F, 0xF1, 0xA0, 0x6A, 0xE8, 0x78, 0x42),
pid = 0
};
resources.GetStream(file.Id, ref property, 0, ref optimalTransferSize, out wpdStream);
System.Runtime.InteropServices.ComTypes.IStream sourceStream =
// ReSharper disable once SuspiciousTypeConversion.Global
(System.Runtime.InteropServices.ComTypes.IStream)wpdStream;
var filename = Path.GetFileName(file.Name);
if (string.IsNullOrEmpty(filename))
return null;
FileStream targetStream = new FileStream(Path.Combine(saveToPath, filename),
FileMode.Create, FileAccess.Write);
try
{
unsafe
{
var buffer = new byte[1024];
int bytesRead;
do
{
sourceStream.Read(buffer, 1024, new IntPtr(&bytesRead));
targetStream.Write(buffer, 0, 1024);
} while (bytesRead > 0);
targetStream.Close();
}
}
finally
{
Marshal.ReleaseComObject(sourceStream);
Marshal.ReleaseComObject(wpdStream);
}
return pdi;
}
}
There are two problems with this standard code:
1) - when the images are saves to the windows machine, there is no EXIF information. this information is what i need. how do i preserve it?
2) the saved files are very bloated. for example, the source jpeg is 1,045,807 bytes, whilst the downloaded file is 3,942,840 bytes!. it is similar to all of the other files. I would of thought that the some inside the unsafe{} section would output it byte for byte? Is there a better way to transfer the data? (a safe way?)
Sorry about this. it works fine.. it is something else that is causing these issues

Android 6 get path to downloaded file

I our app (Xamarin C#) we download files from a server. At the end of a succeful download we get the URI to the newly-downloaded file and from the URI we get the file path:
Android.Net.Uri uri = downloadManager.GetUriForDownloadedFile(entry.Value);
path = u.EncodedPath;
In Android 4.4.2 and in Android 5 the uri and path look like this:
uri="file:///storage/emulated/0/Download/2.zip"
path = u.EncodedPath ="/storage/emulated/0/Download/2.zip"
We then use path to process the file.
The problem is that in Android 6 (on a real Nexus phone) we get a completely different uri and path:
uri="content://downloads/my_downloads/2802"
path="/my_downloads/2802"
This breaks my code by throwing a FileNotFound exception. Note that the downloaded file exists and is in the Downloads folder.
How can I use the URI I get from Android 6 to get the proper file path so I can to the file and process it?
Thank you,
donescamillo#gmail.com
I didn't get your actual requirement but it looks like you want to process file content. If so it can be done by reading the file content by using file descriptor of downloaded file. Code snippet as
ParcelFileDescriptor parcelFd = null;
try {
parcelFd = mDownloadManager.openDownloadedFile(downloadId);
FileInputStream fileInputStream = new FileInputStream(parcelFd.getFileDescriptor());
} catch (FileNotFoundException e) {
Log.w(TAG, "Error in opening file: " + e.getMessage(), e);
} finally {
if(parcelFd != null) {
try {
parcelFd.close();
} catch (IOException e) {
}
}
}
But I am also looking to move or delete that file after processing.
May you an build your URI with the download folder :
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toURI();
It's work. #2016.6.24
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals( action)) {
DownloadManager downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = downloadManager.query(query);
if(c != null) {
if (c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String downloadFileUrl = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
startInstall(context, Uri.parse(downloadFileUrl));
}
}
c.close();
}
}
}
private boolean startInstall(Context context, Uri uri) {
if(!new File( uri.getPath()).exists()) {
System.out.println( " local file has been deleted! ");
return false;
}
Intent intent = new Intent();
intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction( Intent.ACTION_VIEW);
intent.setDataAndType( uri, "application/vnd.android.package-archive");
context.startActivity( intent);
return true;
}

How to generate random bytes via Cryptographic Service Provider (CSP) without .NET/COM?

Is there a way to generate strong random bytes via Microsoft's Cryptographic Service Provider (CSP) without using .NET/COM? For example, using command line or some other way?
I'd like to use it in NodeJS to be more specific.
Refer to http://technet.microsoft.com/en-us/library/cc733055(v=ws.10).aspx
netsh nap client set csp name = <name> keylength = <keylength>
If this command works for you, just exec it through nodejs. (require('child_process').exec)
Yes, using the Windows API. Here is a sample C++ code:
#include "Wincrypt.h"
// ==========================================================================
HCRYPTPROV hCryptProv= NULL; // handle for a cryptographic provider context
// ==========================================================================
void DoneCrypt()
{
::CryptReleaseContext(hCryptProv, 0);
hCryptProv= NULL;
}
// --------------------------------------------------------------------------
// acquire crypto context and a key ccontainer
bool InitCrypt()
{
if (hCryptProv) // already initialized
return true;
if (::CryptAcquireContext(&hCryptProv , // handle to the CSP
NULL , // container name
NULL , // use the default provider
PROV_RSA_FULL , // provider type
CRYPT_VERIFYCONTEXT )) // flag values
{
atexit(DoneCrypt);
return true;
}
REPORT(REP_ERROR, _T("CryptAcquireContext failed"));
return false;
}
// --------------------------------------------------------------------------
// fill buffer with random data
bool RandomBuf(BYTE* pBuf, size_t nLen)
{
if (!hCryptProv)
if (!InitCrypt())
return false;
size_t nIndex= 0;
while (nLen-nIndex)
{
DWORD nCount= (nLen-nIndex > (DWORD)-1) ? (DWORD)-1 : (DWORD)(nLen-nIndex);
if (!::CryptGenRandom(hCryptProv, nCount, &pBuf[nIndex]))
{
REPORT(REP_ERROR, _T("CryptGenRandom failed"));
return false;
}
nIndex+= nCount;
}
return true;
}

How to query Folder Size in remote computer through WMI and C#

How to query Folder Size in remote computer through WMI and C#.
I need to find the each User's folder size in C:\Users in remote System through WMI.
I tried Win32_Directory , CMI_DataFile but not able to find the desired answer.
Please help!!
To get the size of a folder using the WMI, you must iterate over the files using the CIM_DataFile class and then get the size of each file from the FileSize property.
Try this sample (this code is not recursive, I leave such task for you).
using System;
using System.Collections.Generic;
using System.Management;
using System.Text;
namespace GetWMI_Info
{
class Program
{
// Directory is a type of file that logically groups data files 'contained' in it,
// and provides path information for the grouped files.
static void Main(string[] args)
{
try
{
string ComputerName = "localhost";
ManagementScope Scope;
if (!ComputerName.Equals("localhost", StringComparison.OrdinalIgnoreCase))
{
ConnectionOptions Conn = new ConnectionOptions();
Conn.Username = "";
Conn.Password = "";
Conn.Authority = "ntlmdomain:DOMAIN";
Scope = new ManagementScope(String.Format("\\\\{0}\\root\\CIMV2", ComputerName), Conn);
}
else
Scope = new ManagementScope(String.Format("\\\\{0}\\root\\CIMV2", ComputerName), null);
Scope.Connect();
string Drive= "c:";
//look how the \ char is escaped.
string Path="\\\\FolderName\\\\";
UInt64 FolderSize = 0;
ObjectQuery Query = new ObjectQuery(string.Format("SELECT * FROM CIM_DataFile Where Drive='{0}' AND Path='{1}' ", Drive, Path));
ManagementObjectSearcher Searcher = new ManagementObjectSearcher(Scope, Query);
foreach (ManagementObject WmiObject in Searcher.Get())
{
Console.WriteLine("{0}", (string)WmiObject["FileName"]);// String
FolderSize +=(UInt64)WmiObject["FileSize"];
}
Console.WriteLine("{0,-35} {1,-40}", "Folder Size", FolderSize.ToString("N"));
}
catch (Exception e)
{
Console.WriteLine(String.Format("Exception {0} Trace {1}",e.Message,e.StackTrace));
}
Console.WriteLine("Press Enter to exit");
Console.Read();
}
}
}

Tomcat secured static content

I'm making a service that among other has the "photo albums" feature that serve photos to users. User has to be "allowed" to see the photo from the album. So sending the direct link to other person shouldn't allow to view photo.
Photos are stored in the folder outside of the context.
What I need to do is to perform some checks when user requests the photo and then if checks are OK - serve the file. I want to avoid making a wheel and just let tomcat serve the image as it usually does for static files. Can you give some advice on that?
Ok, guys.
After struggling hard with this question I think I've finally found out what to do to solve it. First of all it looks like the question actually decomposes into two independent tasks. One of them is securing access to some resources and second one is feeding resources from the folder outside of the context.
First task is trivial and can be solved by writing a simple filter hanged to "/".
Second task is much less trivial but fortunately also can be resolved. Tomcat uses the implementation of javax.naming.directory.DirContext to load all resources of the given web application including class files. It also allows you to provide the custom implementation of this interface and configure it in the context.xml file. The default implementation is org.apache.naming.resources.FileDirContext. Details here: http://tomcat.apache.org/tomcat-6.0-doc/config/resources.html
I've created my own implementation of DirContext by simply extending FileDirContext. Luckily enough there was a single method that had to be overwritten in order to "hook up" file discovery. The method is called file().
I'm posting my test code here. It is far from perfect and does not take into account the corner cases like renaming files but I don't think that these are needed under a normal run of the server.
The basic idea under this code is to check if the path starts with "virtual directory" prefix and if it is - search for file in the other place in the filesystem (I know there is some duplicate code there but I hope you're not that lazy to remove it if you ever want to use it :-). setVirtualName and setVirtualBase are called automatically to inject the configuration params.
/**
* TODO: add javadocs
*
* #author Juriy Bura
*/
public class VirtualFolderDirContext extends FileDirContext {
private String virtualName;
private String realName;
private File virtualBase;
private String absoluteVirtualBase;
public VirtualFolderDirContext() {
super();
}
public VirtualFolderDirContext(Hashtable env) {
super(env);
}
public void setVirtualName(String path) {
virtualName = path;
}
public void setVirtualBase(String base) {
this.realName = base;
virtualBase = new File(realName);
try {
virtualBase = virtualBase.getCanonicalFile();
} catch (IOException e) {
// Ignore
}
this.absoluteVirtualBase = virtualBase.getAbsolutePath();
}
protected File file(String name) {
File file = null;
boolean virtualFile = name.startsWith(virtualName + "/");
if (virtualFile) {
file = new File(virtualBase, name.substring(virtualName.length()));
} else {
file = new File(base, name);
}
if (file.exists() && file.canRead()) {
if (allowLinking)
return file;
// Check that this file belongs to our root path
String canPath = null;
try {
canPath = file.getCanonicalPath();
} catch (IOException e) {
}
if (canPath == null)
return null;
// Check to see if going outside of the web application root
if (!canPath.startsWith(absoluteBase) && !canPath.startsWith(absoluteVirtualBase)) {
return null;
}
// Case sensitivity check
if (caseSensitive) {
String fileAbsPath = file.getAbsolutePath();
if (fileAbsPath.endsWith("."))
fileAbsPath = fileAbsPath + "/";
String absPath = normalize(fileAbsPath);
if (canPath != null)
canPath = normalize(canPath);
if (virtualFile) {
if ((absoluteVirtualBase.length() < absPath.length())
&& (absoluteVirtualBase.length() < canPath.length())) {
absPath = absPath.substring(absoluteVirtualBase.length() + 1);
if ((canPath == null) || (absPath == null))
return null;
if (absPath.equals(""))
absPath = "/";
canPath = canPath.substring(absoluteVirtualBase.length() + 1);
if (canPath.equals(""))
canPath = "/";
if (!canPath.equals(absPath))
return null;
}
} else {
if ((absoluteBase.length() < absPath.length())
&& (absoluteBase.length() < canPath.length())) {
absPath = absPath.substring(absoluteBase.length() + 1);
if ((canPath == null) || (absPath == null))
return null;
if (absPath.equals(""))
absPath = "/";
canPath = canPath.substring(absoluteBase.length() + 1);
if (canPath.equals(""))
canPath = "/";
if (!canPath.equals(absPath))
return null;
}
}
}
} else {
return null;
}
return file;
}
}
After you have this class in place you have to jar it and put that jar into the Tomcat lib folder. For obvious reasons it cannot go together with war file. In your context.xml you should add a config lines like these:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="true" antiJARLocking="true">
<Resources
className="com.juriy.tomcat.virtualdir.VirtualFolderDirContext"
virtualName="/upload"
virtualBase="c:/temp/up">
</Resources>
...
...
Now any time user asks for /upload/ it will be resolved to c:\temp. With this technique you can implement loading resources from virtually any location: http, shared folder, database, even version control system. So it is pretty cool.
P.S. I've killed the whole day to make this all work together so don't hesitate to give me your vote if you like the answer :-))
Cheers
Juriy

Resources