I'm trying to get a Certificate for an RSA public key to store it in a KeyStore. For this, I use the code below :
certificates=new Certificate[1];
myEncodedPublicKey=myKey.getPublic().getEncoded();
byteStream=new ByteArrayInputStream(myEncodedPublicKey);
certificates[0] = myCertificateFactory.generateCertificate(byteStream);
Despite a non empty encoded public key, I get the following error : Unable to initialize, java.io.IOException: Short read of DER length.
Does anyone know what could be the origin of this problem?
Related
I am developing an Android project.
I have a PEM certificate string:
-----BEGIN CERTIFICATE-----
MIIEczCCA1ugAwIBAgIBADANBgkqhkiG9w0BAQQFAD..AkGA1UEBhMCR0Ix
EzARBgNVBAgTClNvbWUtU3RhdGUxFDASBgNVBAoTC0..0EgTHRkMTcwNQYD
VQQLEy5DbGFzcyAxIFB1YmxpYyBQcmltYXJ5IENlcn..XRpb24gQXV0aG9y
...MANY LINES...
It8una2gY4l2O//on88r5IWJlm1L0oA8e4fR2yrBHX..adsGeFKkyNrwGi/
7vQMfXdGsRrXNGRGnX+vWDZ3/zWI0joDtCkNnqEpVn..HoX
-----END CERTIFICATE-----
(assigned above certificate string to a variable named CERT_STR)
I decode above PEM string to byte array:
byte[] pemBytes = Base64.decode(
CERT_STR.replaceAll("-----(BEGIN|END) CERTIFICATE-----", "")
.replaceAll("\n", "")
.getBytes("UTF-8"),
Base64.DEFAULT
);
I try to programmatically install the PEM certificate to my Android phone by following code:
Intent intent = KeyChain.createInstallIntent();
// because my PEM only contains a certificate, no private key, so I use EXTRA_CERTIFICATE
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, pemBytes);// above PEM bytes
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
When run my code (in Android 7 device), the Android system certificate installer app pops up the window, when I press "OK" button of that window, I got following log:
java.io.IOException: stream does not represent a PKCS12 key store
at com.android.org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.engineLoad(PKCS12KeyStoreSpi.java:793)
at java.security.KeyStore.load(KeyStore.java:1247)
at com.android.certinstaller.CredentialHelper.loadPkcs12Internal(CredentialHelper.java:396)
at com.android.certinstaller.CredentialHelper.extractPkcs12Internal(CredentialHelper.java:364)
at com.android.certinstaller.CredentialHelper.extractPkcs12(CredentialHelper.java:354)
at com.android.certinstaller.CertInstaller$1.doInBackground(CertInstaller.java:328)
at com.android.certinstaller.CertInstaller$1.doInBackground(CertInstaller.java:327)
My questions:
I have used EXTRA_CERTIFICATE & set it to intent, I am NOT using EXTRA_PKCS12, but from the log, Android system thinks I am installing PKCS#12 keystore. Why?
What is the correct way to programmatically install PEM certificate in Android?
Your code should work, as said #Sergey Nikitin. This starred example at Github is using similar code
I have reviewed the Android 7.1 source code of CredentialHelper and CertInstaller to trace your exception log. The unique reachable path to execute the pkcs12 loader at
com.android.certinstaller.CredentialHelper.extractPkcs12(CredentialHelper.java:354)
is the method onScreenlockOk
private void onScreenlockOk() {
if (mCredentials.hasPkcs12KeyStore()) {
if (mCredentials.hasPassword()) {
showDialog(PKCS12_PASSWORD_DIALOG);
} else {
new Pkcs12ExtractAction("").run(this);
}
which is protected by CredentialHelper.hasPkcs12KeyStore()
boolean hasPkcs12KeyStore() {
return mBundle.containsKey(KeyChain.EXTRA_PKCS12);
}
I have not found default assigned values or alternative paths, so I deduce that KeyChain.EXTRA_PKCS12 is being used in some way. It is a weird behaviour, may be you have a clean&rebuild issue?
I suggest to debug the code including Android CertInstaller class to ensure the values of the Extras and ensure that the executed code is the expected
I have a C application, within this application I generate a RSA key pair with the following code (error checking left due to readability):
void generateKeyPair(char* pass) {
EVP_PKEY *pkey = NULL;
RSA* r;
OpenSSL_add_all_algorithms();
RAND_load_file("/dev/urandom", 1024);
r = RSA_generate_key(KEY_LENGTH, RSA_F4, NULL, NULL);
pkey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey, r);
FILE* fp = fopen("private.key", "w");
PEM_write_PrivateKey(fp, pkey, EVP_aes_256_cbc(), NULL, 0, NULL, pass);
fclose(fp);
fp = fopen("public.key", "w");
PEM_write_PUBKEY(fp, pkey);
fclose(fp);
}
I synchronize the public PEM keys through a server between the devices. Now I have to write a compatible Android application though I have to generate the public key in the same format then PEM_write_PUBKEY does.
I know that I should have done the synchronisation in DER format or something but now I can't change the design any more. There is no way around generating the keys in the similar format.
I guess that it is a #PCKS1 base64 encoded key but I am not sure and I don't know how to generate a similar one in Android - Androids standard is #PCKS8. However I would prefer not to include spongy castle in my project if there is a way around.
You are in luck; it seems you only have to PEM encode your public key as both Java and OpenSSL use the same SubjectPublicKey structure used for X5.09 certificates. You can get to this by running RSAPublicKey.encode().
Unfortunately I don't know any other library that performs PEM encoding, but I'm sure you can strip out the code from Bouncy or Spongy if required; the Bouncy Castle libraries have a very liberal licensing structure.
I am trying to search on XMPP. I got the code from here. It works fine and I am able to connect to the server. But its showing the alert window like this
and If I click "Always" or "Once" it is accepting and I am able to show the contacts and chat messages....
Is there any way to stop this alert and can I connect directly to the server?
This message is displayed by MemorizingTrustManager (MTM), an Android library aimed to improve the security/usability trade-off for "private cloud" SSL installations.
MTM issues this warning whenever you connect to a server with a certificate not issued by one of the Android OS trusted Root CAs, like a self-signed certificate or one by CACert.
If the message appears again after you clicked "Always", this is a bug in MTM (probably due to a mismatching SSL server name), and should be reported via github.
Edit: if you are making an app that only communicates with one server, and you know the server's certificate in advance, you should replace MTM with AndroidPinning, which ensures that nobody can make man-in-the-middle attacks on your connection.
Disclaimer: I am the author of MTM and the mainainer of yaxim.
Get the certificate signed by a certificate authority. Forget all coding solutions.
Is there any way to stop this alert and can I connect directly to the server?
Its not clear to me if you wrote this app that connects to kluebook.com. I think you did, but its not explicit.
Assuming you wrote the app and know the server you are connecting to (kluebook.com), you should provide a custom TrustManager to handle this. You can find code for a custom TrustManger that works with the expected server certificate in OWASP's Certificate and Public Key Pinning example. Its OK to pin because you know what the certificate or public key is, and there's no need to trust someone else like a CA.
If you have no a priori knowledge, then you trust on first use and follow with a key continuity strategy looking for abrupt changes in the certificate or public key. In this case, the trust on first use should include the customary X509 checks.
Pinning is part of an overall security diversification strategy. A great book on the subject is Peter Gutmann's Engineering Security.
What you are seeing with the prompt is one leg of the strategy - namely, the Trust-On-First-Use (TOFU) strategy. The prompt has marginal value because user's don't know how to respond to it, so they just click yes to dismiss the box so they can continue what they are doing. Peter Gutmann has a great write-up on user psychology (complete with Security UI studies) in Engineering Security.
From section 6.1 of OWASP's Certificate and Public Key Pinning:
public final class PubKeyManager implements X509TrustManager
{
private static String PUB_KEY = "30820122300d06092a864886f70d0101...";
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
if (chain == null) {
throw new IllegalArgumentException("checkServerTrusted: X509Certificate array is null");
}
if (!(chain.length > 0)) {
throw new IllegalArgumentException("checkServerTrusted: X509Certificate is empty");
}
if (!(null != authType && authType.equalsIgnoreCase("RSA"))) {
throw new CertificateException("checkServerTrusted: AuthType is not RSA");
}
// Perform customary SSL/TLS checks
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init((KeyStore) null);
for (TrustManager trustManager : tmf.getTrustManagers()) {
((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
}
} catch (Exception e) {
throw new CertificateException(e);
}
// Hack ahead: BigInteger and toString(). We know a DER encoded Public Key begins
// with 0x30 (ASN.1 SEQUENCE and CONSTRUCTED), so there is no leading 0x00 to drop.
RSAPublicKey pubkey = (RSAPublicKey) chain[0].getPublicKey();
String encoded = new BigInteger(1 /* positive */, pubkey.getEncoded()).toString(16);
// Pin it!
final boolean expected = PUB_KEY.equalsIgnoreCase(encoded);
if (!expected) {
throw new CertificateException("checkServerTrusted: Expected public key: "
+ PUB_KEY + ", got public key:" + encoded);
}
}
}
}
You can get the expected public key from OpenSSL's s_client, but you have to know the port. I can't get a response from the well known SSL ports like 5223, and 5222 has no security services:
$ openssl s_client -connect kluebook.com:5222
CONNECTED(00000003)
140735088755164:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:s23_clnt.c:766:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 322 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---
Once you get a public key, plug it back into the TrustManager at PUB_KEY.
I have implemented javax.net.ssl.X509TrustManager in my code so I can validate my self-signed cert, which my software accesses. However, I still need to validate some other "standard" website SSL certificates. I am using CertPathValidator.validate() to do that, but I just realized that one cert chain I am being passed (for maps.googleapis.com) doesn't actually contain the complete chain - it contains the whole chain but the Root CA (Equifax), which does exist on the phone, but validate() still fails because (apparently) it's not explicitly in the chain. What am I missing here, so that the validation succeeds? Thanks for any input.
Edit - Relevant code (with exception checking removed):
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// chain is of type X509Certificate[]
CertPath cp = cf.generateCertPath(Arrays.asList(chain));
CertPathValidator cpv = CertPathValidator.getInstance(CertPathValidator.getDefaultType());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream is = new FileInputStream("/system/etc/security/cacerts.bks");
ks.load(is, null);
PKIXParameters params = new PKIXParameters(ks);
CertPathValidatorResult cpvr = cpv.validate(cp, params);
Implementing your own TrustManager is generally a bad idea. The better way is to add your certificates to a keystore, and add it as a trust store. See this for an example of how to do it.
You probably need to add the default trust store to your validator. Also, Android does not do revocation (CRL) checking by default, so if you enabled it, you need to get the related CRL's manually. Post your code.
I am developing an Android Application and I need to generate some RSA private and public keys to use for secure communication with web services. To do this I need to have the public key in a .NET compatible form.
Like:
<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>
So far I managed to to this:
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
keypair = keyGen.genKeyPair();
privateKey = keypair.getPrivate();
publicKey = keypair.getPublic();
// Get the bytes of the public and private keys
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
I've got no clue how to continue. Could you please provide some help ?
For anybody else interested, a very good tutorial can be found in here
http://www.codeproject.com/KB/security/porting_java_public_key.aspx?msg=3407475
If you need Base64 encoding/decoding, because it's not included in Android (at least in API 4) you could use the class from here: iharder.sourceforge.net/current/java/base64/
You don't show the type publicKey. If is not already, you should cast to an RSAPublicKey, then use the getPublicExponent() and getModulus() methods to extract the BigInteger. Then simply use standard Java IO, e.q. PrintStream.println() or printf() to generate the XML components.