Is it possible to access certificates stored in the Local Machine store (rather than Current User) from a Java Servlet? I've tried using the MSCAPI provider opening the "Windows-MY" and "Windows-ROOT" stores, but neither contain certificates from the Local Machine store.
I used used JNA to access the certificates using the same windows dialog that pops up if you were to use any windows specific program - this may not answer your question but certainly lets you provide an option to access anything in a 'windows way':
NativeLibrary cryptUI = NativeLibrary.getInstance("Cryptui");
NativeLibrary crypt32 = NativeLibrary.getInstance("Crypt32");
Function functionCertOpenSystemStore = crypt32.getFunction("CertOpenSystemStoreA");
Object[] argsCertOpenSystemStore = new Object[] { 0, "CA"};
HANDLE h = (HANDLE) functionCertOpenSystemStore.invoke(HANDLE.class, argsCertOpenSystemStore);
Function functionCryptUIDlgSelectCertificateFromStore = cryptUI.getFunction("CryptUIDlgSelectCertificateFromStore");
System.out.println(functionCryptUIDlgSelectCertificateFromStore.getName());
Object[] argsCryptUIDlgSelectCertificateFromStore = new Object[] { h, 0, 0, 0, 16, 0, 0};
Pointer ptrCertContext = (Pointer) functionCryptUIDlgSelectCertificateFromStore.invoke(Pointer.class, argsCryptUIDlgSelectCertificateFromStore);
Function functionCertGetNameString = crypt32.getFunction("CertGetNameStringW");
char[] ptrName = new char[128];
Object[] argsCertGetNameString = new Object[] { ptrCertContext, 5, 0, 0, ptrName, 128};
functionCertGetNameString.invoke(argsCertGetNameString);
System.out.println("Selected certificate is " + new String(ptrName));
Function functionCertFreeCertificateContext = crypt32.getFunction("CertFreeCertificateContext");
Object[] argsCertFreeCertificateContext = new Object[] { ptrCertContext};
functionCertFreeCertificateContext.invoke(argsCertFreeCertificateContext);
Function functionCertCloseStore = crypt32.getFunction("CertCloseStore");
Object[] argsCertCloseStore = new Object[] { h, 0};
functionCertCloseStore.invoke(argsCertCloseStore);
It is just a piece of code that works; feel free to apply your coding practices.
The default JDK implementation is fairly limited. AFAIK it will only bring back RSA keys and certificates. It is not a general purpose adapter to MSCAPI. I have been able to get some certs back using the mechanism you describe.
As others have mentioned, the MSCAPI provider does not provide access to certificates and keys stored in the "Local Computer" certificate store. The reason for this is that MSCAPI uses the Microsoft CryptoAPI function CertOpenSystemStore to access the certificates and keys. The documentation for this function explicitly states that "Only current user certificates are accessible using this method, not the local machine store". You can follow this OpenJDK bug if you want to track progress on this issue.
If you want a proper solution to the problem you can purchase the commercial Pheox JCAPI library.
If you can live with a hack, I have created a simple utility that intercepts the JDKs call to CertOpenSystemStore and returns a handle to a virtual certificate store allowing read-only access to the certificates and keys in both the "Current User" and "Local Machine" certificate stores. This solved my problem, but be aware of the limitations of this utility.
The certificates you are looking for are in the java keystore file or are passed into tomcat when starting the server
http://tomcat.apache.org/tomcat-4.0-doc/ssl-howto.html
if you are trying to load them in your application, then look here for to make HTTPS requests, then the HTTPClient documentation will get you started
http://www.jdocs.com/httpclient/3.0.1/api-index.html?m=class&p=org.apache.commons.httpclient.contrib.ssl&c=AuthSSLProtocolSocketFactory&render=classic
not sure if this helps you out, but if you can provide more details, then you might be able to get a more specific answer
public class KeyStoreLookup {
public static void main(String args[]) {
try {
KeyStore ks =
KeyStore.getInstance(KeyStore.getDefaultType());
String fname = System.getProperty("user.home") +
File.separator + ".keystore";
FileInputStream fis = new FileInputStream(fname);
ks.load(fis, null);
if (ks.isKeyEntry(args[0])) {
System.out.println(args[0] +
" is a key entry in the keystore");
char c[] = new char[args[1].length()];
args[1].getChars(0, c.length, c, 0);
System.out.println("The private key for" + args[0] +
" is " + ks.getKey(args[0], c));
Certificate certs[] = ks.getCertificateChain(args[0]);
if (certs[0] instanceof X509Certificate) {
X509Certificate x509 = (X509Certificate) certs[0];
System.out.println(args[0] + " is really " +
x509.getSubjectDN());
}
if (certs[certs.length - 1] instanceof
X509Certificate) {
X509Certificate x509 = (X509Certificate)
certs[certs.length - 1];
System.out.println(args[0] + " was verified by " +
x509.getIssuerDN());
}
}
else if (ks.isCertificateEntry(args[0])) {
System.out.println(args[0] +
" is a certificate entry in the keystore");
Certificate c = ks.getCertificate(args[0]);
if (c instanceof X509Certificate) {
X509Certificate x509 = (X509Certificate) c;
System.out.println(args[0] + " is really " +
x509.getSubjectDN());
System.out.println(args[0] + " was verified by " +
x509.getIssuerDN());
}
}
else {
System.out.println(args[0] +
" is unknown to this keystore");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Related
I'm looking to write something that can enumerate and use (to sign) certificates in CurrentUser/My and LocalMachine/My, but I haven't been able to find anything for the Windows cert store, only Java's own secret store. This link looks promising, but I can only use what ships with Java.
I found this question asked on SO before, but it's from five years ago, which is a long time in computer years. Thanks!
Start Java with -Djavax.net.ssl.trustStoreType=WINDOWS-ROOT.
See https://www.oracle.com/technical-resources/articles/javase/security.html for more information.
The cross-platform nature of the Java has its own downsides -- you cannot access some (or many) OS-specific things without external libraries. Windows certificate store is accessible only via CryptoAPI native functions which are not support by Java default installation.
You may take a look at this thread: Calling Win32 API method from Java
If you can use JNA, then you can use various Certificate and Certificate Store Functions in crypt32.dll to enumerate certificates and perform signing operations.
KeyStore keyStore = KeyStore.getInstance(getKeyStoreType(), "SunMSCAPI");
keyStore.load(null, null);
try {
Field field = keyStore.getClass().getDeclaredField("keyStoreSpi");
field.setAccessible(true);
KeyStoreSpi keyStoreVeritable = (KeyStoreSpi)field.get(keyStore);
field = keyStoreVeritable.getClass().getEnclosingClass().getDeclaredField("entries");
field.setAccessible(true);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Set accessible keyStoreSpi problem", e);
}
Enumeration enumeration = keyStore.aliases();
I picked from where Crypt32 left, used JNA to access the certificates using the same windows dialog that pops up if you were to use any windows specific program:
NativeLibrary cryptUI = NativeLibrary.getInstance("Cryptui");
NativeLibrary crypt32 = NativeLibrary.getInstance("Crypt32");
Function functionCertOpenSystemStore = crypt32.getFunction("CertOpenSystemStoreA");
Object[] argsCertOpenSystemStore = new Object[] { 0, "CA"};
HANDLE h = (HANDLE) functionCertOpenSystemStore.invoke(HANDLE.class, argsCertOpenSystemStore);
Function functionCryptUIDlgSelectCertificateFromStore = cryptUI.getFunction("CryptUIDlgSelectCertificateFromStore");
System.out.println(functionCryptUIDlgSelectCertificateFromStore.getName());
Object[] argsCryptUIDlgSelectCertificateFromStore = new Object[] { h, 0, 0, 0, 16, 0, 0};
Pointer ptrCertContext = (Pointer) functionCryptUIDlgSelectCertificateFromStore.invoke(Pointer.class, argsCryptUIDlgSelectCertificateFromStore);
Function functionCertGetNameString = crypt32.getFunction("CertGetNameStringW");
char[] ptrName = new char[128];
Object[] argsCertGetNameString = new Object[] { ptrCertContext, 5, 0, 0, ptrName, 128};
functionCertGetNameString.invoke(argsCertGetNameString);
System.out.println("Selected certificate is " + new String(ptrName));
Function functionCertFreeCertificateContext = crypt32.getFunction("CertFreeCertificateContext");
Object[] argsCertFreeCertificateContext = new Object[] { ptrCertContext};
functionCertFreeCertificateContext.invoke(argsCertFreeCertificateContext);
Function functionCertCloseStore = crypt32.getFunction("CertCloseStore");
Object[] argsCertCloseStore = new Object[] { h, 0};
functionCertCloseStore.invoke(argsCertCloseStore);
It is just a piece of code that works; feel free to apply your coding practices.
I have a problem listing all available certificates installed in my personal (windows) keystore.
I use following code to get a list of all certificate aliases:
public static void main(String[] args) {
try {
KeyStore ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
Enumeration<String> en = ks.aliases();
while (en.hasMoreElements()) {
String aliasKey = (String) en.nextElement();
System.out.println("---> alias '" + aliasKey + "'");
//TODO GET CERT ETC
}
} catch (Exception ioe) {
System.err.println(ioe.getMessage());
}
}
It works fine, listing 2 of my recently installed certificates.
HOWEVER.
After a computer restart, code lists only one certificate alias, though in system (using certmgr.msc) i still have two certificates active.
Whats more - installed certificates are from smartcards:
- ENCARD (with ENIGMA CAPI)
- UNIZETO CARD (dont know details)
Problem seems to occur, when i use ENCARD with API.
After restart, UNIZETO card still works fine.
Any ideas?
I have a tomcat-hibernate-hsqldb setup and I want to use SSL to secure data transfer between my application and hsqldb. However, I need to pre install a certificate which can be used at any deployment. I do not want to use a new certificate for each new deployment site. For this, if I just use a self-signed certificate issues to any random Common Name and then install the same certificate in the trust store of tomcat, then I get this exception
java.net.UnknownHostException: Certificate Common Name[random name] does not match host name[192.168.100.10]
I need to disable hostname verification in this setup, but all the info I found on web points to the mechanism of disabling it for HttpsURLConnection.
I believe hsqldb has a custom code to do it, in the file
org.hsqldb.serverHsqlSocketFactorySecure
Here is the method, which does this:
protected void verify(String host, SSLSession session) throws Exception {
X509Certificate[] chain;
X509Certificate certificate;
Principal principal;
PublicKey publicKey;
String DN;
String CN;
int start;
int end;
String emsg;
chain = session.getPeerCertificateChain();
certificate = chain[0];
principal = certificate.getSubjectDN();
DN = String.valueOf(principal);
start = DN.indexOf("CN=");
if (start < 0) {
throw new UnknownHostException(
Error.getMessage(ErrorCode.M_SERVER_SECURE_VERIFY_1));
}
start += 3;
end = DN.indexOf(',', start);
CN = DN.substring(start, (end > -1) ? end
: DN.length());
if (CN.length() < 1) {
throw new UnknownHostException(
Error.getMessage(ErrorCode.M_SERVER_SECURE_VERIFY_2));
}
if (!CN.equalsIgnoreCase(host)) {
// TLS_HOSTNAME_MISMATCH
throw new UnknownHostException(
Error.getMessage(
ErrorCode.M_SERVER_SECURE_VERIFY_3, 0,
new Object[] {
CN, host
}));
}
}
Is there a way to somehow bypass this mechanism and disable hostname validation?
Asked the same question on hsqldb forums and got to know that there is no workaround to this. The only thing you could do is to comment out the code which is calling the verify method and then rebuild the jar. I am still puzzled why hsqldb didn't use the HostnameVerifier (http://docs.oracle.com/javase/6/docs/api/javax/net/ssl/HostnameVerifier.html), which would have made it easier to write a custom Hostname Verifier.
I recently got the book "Pro Paypal E-Commerce" by Damon Williams. Its a 2007 copy, so its to be expected that some things, like the code, would change over time.
I'm trying to get this code below to work. I downloaded the paypal_base.jar file and also the paypal_wpstoolkit.jar and put them into my lib folder under jakarta-tomcat (where all my other jars are). I'm having trouble compiling the code.
This code example comes from the book and also http://en.csharp-online.net/Encrypted_Website_Payments%E2%80%94Using_the_PayPal_Java_SDK
I modified it slightly.
import com.paypal.sdk.profiles.EWPProfile;
import com.paypal.sdk.profiles.ProfileFactory;
import com.paypal.wpstoolkit.services.EWPServices;
import com.paypal.sdk.exceptions.PayPalException;
public class PaypalTest {
// path to your PKCS12 file
public static final String PKCS12 = "./Certs/my_pkcs12.p12";
// path to PayPal's public certificate
public static final String PAYPAL_CERT = "./Certs/paypal_cert_pem.txt";
// use https://www.sandbox.paypal.com if testing
//public static final String URL = "https://www.paypal.com";
public static final String URL = "https://sandbox.paypal.com";
public static void main (String args[]) {
// Check to see if the user provided a password
if (args.length != 1) {
System.out.println("You must provide a password.");
System.exit(0);
}
// password used to encrypt your PKCS12 files
// obtained from the command line
String USER_PASSWORD = args[0];
// First we will create the EWPProfile object
try {
com.paypal.sdk.profiles.EWPProfile ewpProfile = ProfileFactory.createEWPProfile();
ewpProfile.setCertificateFile(PKCS12);
ewpProfile.setPayPalCertificateFile(PAYPAL_CERT);
ewpProfile.setPrivateKeyPassword(USER_PASSWORD);
ewpProfile.setUrl(URL);
String buttonParameters = "cmd=_xclick\nbusiness=buyer#hotmail.com\nitem_name=vase\nitemprice=25.00";
// Next we will create the EWPServices object
// and tell it which EWPProfile object to use
EWPServices ewpServices = new EWPServices();
ewpServices.setEWPProfile(ewpProfile);
// Finally we are ready to call the method to perform the button encryption
String encryptedButton = ewpServices.encryptButton(buttonParameters.getBytes());
System.out.println(encryptedButton);
} catch (PayPalException ppe) {
System.out.println("An exception occurred when creating the button.");
ppe.printStackTrace();
}
}
}//class
The errors I'm getting during compilation are as follows -
java:51: cannot find symbol
symbol: method setEWPProfile(com.paypal.sdk.profiles.EWPProfile)
location: class com.paypal.wpstoolkit.services.EWPServices
ewpServices.setEWPProfile(ewpProfile);
java:55: encryptButton(byte[],java.lang.String,java.lang.String.,java.lang.String.,java.lang.String) in com.paypal.wpstoolkit.services.EWPServices cannot be applied to (byte[])
ewpServices.encryptButton(buttonParameters.getBytes());
The paypal_base jar only has NVPCallerServices.class in it, and not EWPServices. EWPServices is in the wpstoolkit jar.
How do I fix my errors? I'm having trouble finding documentation on the paypal classes.
The updated Java SDK + API documenatation can be found here:
https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_Java_NVP_SDK.zip
Extract that .zip and open docs/index.html
That is where you can find all of the API documentation. It looks like you are trying to make calls to methods that no longer exist. Have a look through the new classes and see what will work for you.
Looks like with the newer API's Paypal want you to have all button code generated from their web service, as they seem to have removed the EWPService class from the SDK. But then I noticed they still provide the client side utility with with you can manually generate the code here. After a little tweaking, I got the code in there to do what I needed (encrypt an upload cart button locally).
Assuming you use Java 5+, just make sure you this and this in your classpath. Now the code isn't perfect, as it contains a bunch of deprecated methods, but for such a trivial task as encrypting the button code, it works just fine.
public String getButtonEncryptionValue(String _data,
String _privateKeyPath, String _certPath, String _payPalCertPath,
String _keyPass) throws IOException, CertificateException,
KeyStoreException, UnrecoverableKeyException,
InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, CertStoreException, CMSException {
_data = _data.replace(',', '\n');
CertificateFactory cf = CertificateFactory.getInstance("X509", "BC");
// Read the Private Key
KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
ks.load(new FileInputStream(_privateKeyPath), _keyPass.toCharArray());
String keyAlias = null;
Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
keyAlias = aliases.nextElement();
}
PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias,
_keyPass.toCharArray());
// Read the Certificate
X509Certificate certificate = (X509Certificate) cf
.generateCertificate(new FileInputStream(_certPath));
// Read the PayPal Cert
X509Certificate payPalCert = (X509Certificate) cf
.generateCertificate(new FileInputStream(_payPalCertPath));
// Create the Data
byte[] data = _data.getBytes();
// Sign the Data with my signing only key pair
CMSSignedDataGenerator signedGenerator = new CMSSignedDataGenerator();
signedGenerator.addSigner(privateKey, certificate,
CMSSignedDataGenerator.DIGEST_SHA1);
ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
certList.add(certificate);
CertStore certStore = CertStore.getInstance("Collection",
new CollectionCertStoreParameters(certList));
signedGenerator.addCertificatesAndCRLs(certStore);
CMSProcessableByteArray cmsByteArray = new CMSProcessableByteArray(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cmsByteArray.write(baos);
System.out.println("CMSProcessableByteArray contains ["
+ baos.toString() + "]");
CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true,
"BC");
byte[] signed = signedData.getEncoded();
CMSEnvelopedDataGenerator envGenerator = new CMSEnvelopedDataGenerator();
envGenerator.addKeyTransRecipient(payPalCert);
CMSEnvelopedData envData = envGenerator.generate(
new CMSProcessableByteArray(signed),
CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC");
byte[] pkcs7Bytes = envData.getEncoded();
return new String(DERtoPEM(pkcs7Bytes, "PKCS7"));
}
public static byte[] DERtoPEM(byte[] bytes, String headfoot) {
ByteArrayOutputStream pemStream = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(pemStream);
byte[] stringBytes = Base64.encode(bytes);
System.out.println("Converting " + stringBytes.length + " bytes");
String encoded = new String(stringBytes);
if (headfoot != null) {
writer.print("-----BEGIN " + headfoot + "-----\n");
}
// write 64 chars per line till done
int i = 0;
while ((i + 1) * 64 < encoded.length()) {
writer.print(encoded.substring(i * 64, (i + 1) * 64));
writer.print("\n");
i++;
}
if (encoded.length() % 64 != 0) {
writer.print(encoded.substring(i * 64)); // write remainder
writer.print("\n");
}
if (headfoot != null) {
writer.print("-----END " + headfoot + "-----\n");
}
writer.flush();
return pemStream.toByteArray();
}
An easier way to do this is not to encrypt, but use an unencrypted button and then a hash trick to detect tampering. I explain this here with PHP, but you can translate to Java.
How do I make a PayPal encrypted buy now button with custom fields?
I'm developing on an application at the moment which contains quite a lot of personal user information - things like Facebook contacts, etc ... Now, one of the things I want to be able to do (and have done, quite effectively) is open up parts of the application to "3rd Party" applications, using Android's build-in inter-process communication protocol (AIDL). So far so good.
Here's the catch: because we're involved in handling quite a lot of personal information, we have to be quite careful about who can and can't access it; specifically, only "Trusted" applications should be able to do so. So the natural way to do this is to use a custom permission within the AndroidManifest.xml file where we declare the services. My problem is this: I want to be able to enact signature-level protection (similar to the normal "signature" permission level), but with a bit of a catch:
I don't only want application signed with our internal signature to be able to access the services. I'd like to be able to build a list of "trusted signatures" & at runtime (or if there's a better way, then maybe some other time?) be able to check incoming requests against this list of trusted keys.
This would satisfy the security constraints in the same way as the normal "signature" permission level I think - only programs on the "trusted keys list" would be able to access the services, and keys are hard to spoof (if possible at all?) - but with the added bonus that we wouldn't have to sign every application making use of the APIs with our internal team's key.
Is this possible at the moment in Android? And if so, are there any special requirements?
Thanks
I've now found the answer to this question, but I'll leave it for the sake of anyone looking in the future.
I opened up a discussion on android-security-discuss where it was answered. Link: http://groups.google.com/group/android-security-discuss/browse_thread/thread/e01f63c2c024a767
Short answer:
private boolean checkAuthorised(){
PackageManager pm = getPackageManager();
try {
for (Signature sig :
pm.getPackageInfo(pm.getNameForUid(getCallingUid()),
PackageManager.GET_SIGNATURES).signatures){
LogUtils.logD("Signature: " + sig.toCharsString());
if (Security.trustedSignatures.get(sig.toCharsString()) != null) {
return true;
}
}
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LogUtils.logD("Couldn't find signature in list of trusted keys! Possibilities:");
for(String sigString : Security.trustedSignatures.keySet()){
LogUtils.logD(sigString);
}
/* Crash the calling application if it doesn't catch */
throw new SecurityException();
}
Where Security.trustedSignatures is a Map of the form:
Map<String,String>().put("public key","some description eg. name");
Put this method inside any code that is being called by the external process (ie. within your interface). Note that this will not have the desired effect inside the onBind() method of your RemoteService.
Great info jelford, but I would suggest instead of storing the entire string of the signature, to store/compare the SHA-1 of the certificate as shown in this answer from matreshkin.
This is similar to how Google handles the Maps Android API, and this will match the output shown via keytool.
private boolean checkAuthorized() throws SecurityException {
PackageManager pm = getPackageManager();
try {
PackageInfo packageInfo = pm.getPackageInfo(pm.getNameForUid(getCallingUid()),
PackageManager.GET_SIGNATURES);
Signature[] signatures = packageInfo.signatures;
byte[] certBytes = signatures[0].toByteArray();
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(
new ByteArrayInputStream(certBytes));
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] encodedCert = md.digest(cert.getEncoded());
String hexString = byte2HexFormatted(encodedCert);
Log.d("public certificate SHA-1: " + hexString);
String trustedAppName = trustedCerts.get(hexString);
if (trustedAppName != null) {
Log.d("Found public certificate SHA-1 for " + trustedAppName);
return true;
}
} catch (Exception e) {
Log.e(e, "Unable to get certificate from client");
}
Log.w("Couldn't find signature in list of trusted certs!");
/* Crash the calling application if it doesn't catch */
throw new SecurityException();
}
public static String byte2HexFormatted(byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1) h = "0" + h;
if (l > 2) h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1)) str.append(':');
}
return str.toString();
}