I am working on a distributed application with a number of uniquely identified slave processes that will communicate with a master application via SSL enabled sockets. The application is written in java.
I need some help understanding SSLSockets, or rather, the certificates they use.
What i am looking for is someone who can tell me if i have understood the basic workings of certificate chains correctly, but i wouldn't say no to a code sample either.
I would like a setup where the server itself has a CA signed certificate, and every slave will get their own certificate created by the master application.
CA->Main server cert->Master SSL cert
CA->Main server cert->Slave SSL cert 1
CA->Main server cert->Slave SSL cert 2
CA->Main server cert->Slave SSL cert 3
First question: Is this kind of certificate chain the correct way to tackle the problem?
I am thinking this is the simplest way of achieving the master and slaves all have a unique identity without having to CA sign every certificate.
Second question:
How do i programatically go about creating an SSL certificate in java? I am trying to create the last certificate in the chain here, assuming i already have the "Main server cert" for now.
I have gotten so far as generating a key for the certificate (Where type is RSA):
public KeyPair generateKeypair(String type, int bytes)
throws NoSuchAlgorithmException{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(type);
keyPairGenerator.initialize(bytes);
return keyPairGenerator.generateKeyPair();
}
X509Principal issuer = PrincipalUtil.getSubjectX509Principal(serverCert);
SubjectPublicKeyInfo key
= SubjectPublicKeyInfo.getInstance(kpair.getPublic().getEncoded());
X509v3CertificateBuilder certGen
= new X509v3CertificateBuilder(
issuer,
BigInteger.valueOf(new SecureRandom().nextInt()),
before,
after,
subject,
key
);
AlgorithmIdentifier sigAlgId
= new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId
= new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
I don't assume that setting the serverCert as the issuer is enough to sign the certificate? As far as i have understood i need to sign the new certificate with the next certificate in the chain somehow, but how do i do that? Do i sign the certificate with the serverCert's private key like:
AsymmetricKeyParameter akp
= PrivateKeyFactory.createKey(serverPrivateKey.getEncoded());
AlgorithmIdentifier sigAlgId
= new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId
= new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
ContentSigner sigGen
= new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(akp);
Are there any other steps i missed?
From a technical point of view your solution is correct. However do not forget the security considerations: who can request a certificate, how the authentication is performed, how the certificates/private keys are distributed to the servers...
These elements are mandatory for a certificate generation:
Subject Name
Issuer name
certificate serial number
subject public key
validity dates (not before, not after)
It is also a good practice to add some extensions:
Subject Key Identifier
Authority Key Indentifier
Basic Constraints
Key Usage
Extended Key Usages
This code snippet outlines the certificate generation:
ContentSigner getCertSigner(PrivateKey issuerKey) {
AsymmetricKeyParameter akp = PrivateKeyFactory.createKey(issuerKey.getEncoded());
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
return new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(akp);
}
X509CertificateHolder generateCertificate(X509Certificate issuerCert, PrivateKey issuerKey, X500Name subject, PublicKey subjectKey, Date notBefore, Date notAfter) {
X509Principal issuerDN = PrincipalUtil.getSubjectX509Principal(issuerCert);
SubjectPublicKeyInfo key = SubjectPublicKeyInfo.getInstance(subjectKey.getEncoded());
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issuerDN, BigInteger.valueOf(new SecureRandom().nextInt()), before, after, subject, key);
// Add authority key identifier
builder.addExtension(X509Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils.createAuthorityKeyIdentifier(issuerCert));
// Add subject key identifier
builder.addExtension(X509Extension.subjectKeyIdentifier, false, JcaX509ExtensionUtils.createSubjectKeyIdentifier(subjectKey));
// Add basic constraints
builder.addExtension(X509Extension.basicConstraints, true, new BasicConstraints(false));
// Add key usage
KeyUsage keyUsage = new KeyUsage(KeyUsage.keyEncipherment|KeyUsage.digitalSignature);
builder.addExtension(X509Extension.keyUsage, true, keyUsage);
// Add extended key usage
ExtendedKeyUsage extKeyUsage = new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth);
builder.addExtension(X509Extension.extendedKeyUsage, false, extKeyUsage);
return builder.build(getCertSigner(issuerKey));
}
UPDATE: fixed the code according to Martin Nielsen's comment.
Related
I am receiving the following String from a certificate stored in Azure Key Vault. I am using the Secret API in order to retrieve both the certificate and the private key related to this cert.
Initially the certificate was uploaded using a .pfx file to Azure Key vault. Now I need to create a Certificate and a PrivateKey to allow client authentication to a 3rd party system and I am using the given String retrieved from the API, however I am note sure how to get around that in Java.
I took some hints from this link in C# however I am pretty certain that this method doesn't work like that in Java. In particular an X509Certificate or a Certificate in general doesn't hold any information about the PrivateKey in Java, unlike C#, and I am not sure how to extract that information from given String in Java.
This works as expected to retrieve the certificate from the String retrieved from the API
String secret = azureSecret.getValue();
byte[] certkey = Base64.getDecoder().decode(secret);
ByteArrayInputStream inputStream = new ByteArrayInputStream(certkey);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(inputStream);
The azureSecret.getValue() format is like the following however I am not sure how to get PrivateKey out of the given String
MIIKvgIBaaZd6Euf3EYwYdHrIIKYzCC...
YES, Java X509Certificate and Certificate is only the certificate. Instead use KeyStore which can contain multiple entries each of which is either a 'trusted' certificate (for someone else), or a privatekey plus certificate plus other chain cert(s) (if applicable) for yourself, or (not relevant here) a 'secret' (symmetric) key. PKCS12 is supported as one type of KeyStore along with others not relevant here, so after the base64-decoding you already have do something like:
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inputstreamfromvaultvalue, password);
// then
PrivateKey pkey = (PrivateKey) ks.getKey(alias, password);
// and
Certificate cert = ks.getCertificate(alias); // if you only need the leaf cert
// or
Certificate[] chain = ks.getCertificateChain(alias); // usually
But if you want to do client authentication in TLS/SSL (including HTTPS), you give the JSSE KeyManager the whole keystore object not the individual pieces (privatekey and certificates). Similarly to verify the peer in TLS/SSL, you give TrustManager a keystore containing trusted certificates, usually root CAs and often defaulted to a built-in set of public root CAs.
I am making a solution for encryption of files via combination of RSA and AES. RSA is basically used for handshaking here to encrypt the symmetric key and decrypt at the receiver end via key pairs.
I have used Java Keystores for Private keys and Self-Signed certificates for Public Keys. Both are created as below:
KEY STORE
keytool -genkey -keyalg RSA -alias BANK -keystore receiverKeystore.jks -validity 360 -keysize 2048
CERTIFICATE EXPORT
keytool -export -alias BANK -keystore receiverKeystore.jks -rfc -file bankCert.cer
Similarly both are used in Java as below:
Private Key
private void setKeys() {
KeyStore senderKeyStore = null;
String keyStorePassword = "fms123";
try {
senderKeyStore = KeyStore.getInstance( "JKS" );
FileInputStream is = new FileInputStream( keystorePath );
senderKeyStore.load( is, keyStorePassword.toCharArray() );
senderPrivateKey = ( PrivateKey ) senderKeyStore.getKey( KEYSTORE_ALIAS, keyStorePassword.toCharArray() );
receiverPublicKey = loadReceiverPublicKeyFromCertificate();
}
catch ( Exception e ) {
System.out.println( " Exception in setting keys from key store = " + e );
}
}
PUBLIC KEY
private PublicKey loadReceiverPublicKeyFromCertificate () {
PublicKey publicKey = null;
try {
FileInputStream fin = new FileInputStream( certificateFilePath );
CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
X509Certificate certificate = ( X509Certificate ) factory.generateCertificate( fin );
publicKey = certificate.getPublicKey();
}
catch ( Exception e ) {
System.out.println( " Exception in loading receiver certificate = " + e );
}
return publicKey;
}
I have now following Questions:
Is the the correct way do it?
If I use a trusted certificate here, how will the public key will be fetched from it? Will it be fetched in the same way? and for private keys? will I use the same approach or something from the certificate?
How can I distribute my certificate to the receiver? whats the secure communication shall for that?
I am new to encryption, will be very glad to get some advises.
Your handshaking is called key establishment. Handshaking is used for (online) transport modes, and I don't think you're developing that. I'd use a PKCS#12 key store instead, JKS is kinda deprecated. The whole idea of using a char[] for the password is that you can delete its contents directly after using it, so using a String as input is not a good idea. Strings are usually interned and always immutable in Java, so you cannot destroy them without ugly hacks involving native code.
Trusted certificates are no different from untrusted certificates. Yes, you can retrieve the public key from it. The private keys are commonly stored together with the certificate chain leading to the leaf user certificate that the private key belongs to. However, the private key is not part of the certificate, and therefore you cannot retrieve a private key from it. That's OK though, private keys should be established on the location where they are needed; there is no need to transport them other than for backup purposes.
How you can send the certificate to the receiver is up to you. The tricky part is to have the receiver trust the certificate. It's valid to use a pre-established key such as a trusted PGP key . If you haven't got such a key then you are in trouble though. What you can do is to use other methods to establish trust. Let the other party call you and then verify that the certificate fingerprint (basically just a hash over the certificate) is correct is probably one of the easiest ways. Bringing it over in person is of course also an option, if your users are not too far away.
You cannot create a secure channel out of thin air though. If you cannot trust who is on the other side, then establishing full trust is obviously impossible.
Is it possible to change the certificate and certificate chain for existing entry (private key) in keystore. As I understand, firstly I need to delete existing entry and then store same private key with new certificate and chain. This solution is not an option since I don't know the password for that entry in keystore.
Solution so far:
Key generation and initial storage:
KeyPair kp = generateRSAKeyPair();
X509Certificate selfSignedCert = makeSelfSignedCert(kp);
ks.load(...);
ks.setKeyEntry("entry1", kp.getPrivate(), PASSWORD, new X509Certificate[]{selfSignedCert});
ks.store(...);
After CA sends certificate I want:
ks.load(...);
ks.setCertificateEntry("entry1", caSignedCert);
ks.store(...);
The main issue is that I don't have the right certificate at the time when the key is being stored to keystore. There is a delay between the generation of keys and when CA sends signed certificates.
Another option would be to not set certificate at all, and after CA signs CSR, add the final signed certificate to keystore. Something like:
KeyPair kp = generateRSAKeyPair();
ks.load(...);
ks.setKeyEntry("entry1", kp.getPrivate(), PASSWORD);
ks.store(...);
// after some time
ks.load(...);
ks.setCertificateEntry("entry1", caSignedCert);
ks.store(...);
But I couldn't find a way to store private key without an associated certificate in keystore. Storing private key elsewhere else is also not an option.
A potential solution is to store signed certificate from CA until a user provides the password to keystore entry and then deleting existing entry & creating new entry with proper certificates. This solution is not ideal and I would like to avoid going down this road.
Any suggestions are welcome.
The solution is to first store key to keystore with the self-signed certificate. At this time, this key cannot be used because CA did not sent the proper certificate for this key yet. If one would use key at this time for signing, the associated self-signed certificate would be used (which is not desirable).
After some time CA sends the proper certificate, which must be stored somewhere (database in my case).
On the first opportunity when the password for key entry is supplied, key is retrieved from keystore and immediately gets stored again with the same password but different alias and proper certificate from CA. Then it is safe to delete original entry with self-signed certificate.
I hope this helps someone. Any better solutions are welcome.
I have initiated an HTTPS connection using something like
httpsCert.connect();
where httpsCert is HttpsURLConnection httpsCert.
Now I do something like Certificate[] certs = httpsCert.getServerCertificates(); to retrieve the server x.509 certificates.
I want to retrieve which root CA or the intermediary CA signed the certificate received above.
My approach has been to look at the issuer field in the certificate received above, but is it a good approach. I mean is there a better approach of doing this ?
Secondly, from developer.android.com/HttpsUrlConnection, it appears that getServerCertificates () would return the list of certificates identifying the peer with the peer's identity certificate followed by CAs. In this context, in case of some certificates, like certificates fetched by Google apps, there is a section of the certificate that says Authority Information Access:
CA Issuers - URI:http://pki.google.com/GIAG2.crt however this is not the case with other apps fetching certificates signed by other authorities.
Second question is how does Android validate the certificate path for certificates signed by intermediary CAs ?
I want to retrieve which root CA or the intermediary CA signed the certificate received above.
If you are talking about missing certificates, then you can't. This is a well known problem in PKI called the "which directory" problem. The problem is you don't know where to look to find the missing certificates. The problem is resolved by the server sending all the certificates you need to build the chain to perform the validation.
You still have to root trust somewhere; otherwise the bad guy would send you the chain he wants you to trust and you would be no wiser. That's why user agents like browsers and cURL carry around a list like cacert.pem.
In fact, some poorly configured servers will not send the needed intermediate certificates. In this case, the browsers carry around a list of intermediates to fill in the missing pieces.
Also see Peter Gutmann's Engineering Security.
My approach has been to look at the issuer field in the certificate received above, but is it a good approach. I mean is there a better approach of doing this ?
That's roughly how the chain is built. When you use the issuer's name, you are using a Distinguished Name (Issuer DN in "directory speak"). There's a Subject DN, too. The Issuer is the authority, and the subject is the entity that its issued to.
When validating a chain, its called "path building". That leads you to RFC 4158, Internet X.509 Public Key Infrastructure: Certification Path Building.
The DN alone is usually not enough because a bad guy can re-use the same name and you would be no wiser. So you often use the Authority Key Identifier (AKI in "directory speak"), which is a thumbprint or digest of the the issuer's public key. The bad guy cannot do useful things by forging the AKI because he does not have the private key to go along with it.
Other things used to make the tuple unique is the Serial Number. This is important when a CA re-issues a certificate using the same DN and same public key - only the serial number differs. You will see this happen when the hash is changed from SHA-1 to SHA-256.
Re-issuing and changing only the hash has happened in the past. Its one of the more difficult path validation failures to track down because everything looks OK on the surface. It takes you a while to realize the DN and AKI are OK, but the SN does not match.
Second question is how does Android validate the certificate path for certificates signed by intermediary CAs ?
Android is Java, and Java follows the RFCs. Here are the three RFCs you need to consult. Its not a small topic, and you could write a book providing the full treatment:
RFC 5280, Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
RFC 6125, Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS)
RFC 4158, Internet X.509 Public Key Infrastructure: Certification Path Building
The "Internet X.509 Public Key Infrastructure" is also referred to as PKIX. Its the Internet's PKI profile. Other organization's PKI's can and sometimes will be a different. That just means other organizations may have different rules than what PKIX uses and documents in the RFCs.
Note well: a "CA root" will be self-signed. The Subject DN will be the same as the Issuer DN, the Subject's Key Identifier (SKI in "directory speak") will be the issuer's Authority Key Identifier (AKI in "directory speak"), etc. In addition, basic constraints will have CA=true and it will probably be marked critical.
An intermediate CA certificate will be issued against (or chained to) a distinct certificate, so Subject DN will not be the same as Issuer DN. But like the self-signed root, basic constraints will have CA=true and it will probably be marked critical.
Its perfectly acceptable for you - as a relying party - to trust an intermediate but not a root, even if the intermediate was certified by the root. That's your prerogative.
Authority Information Access: CA Issuers is stored in CA Extensions.
This code prints the list of urls from Authority Information Access Extension. Thats what you need.
import sun.security.util.ObjectIdentifier;
import sun.security.x509.X509CertImpl;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class readCert{
public boolean isExtAuthorityInfoAccess(Extension ext){
Pattern re = Pattern.compile("\\bcaIssuers\\b",Pattern.CASE_INSENSITIVE);
Matcher m = re.matcher(ext.toString());
if (m.find()) {
return true;
} else {
return false;
}
};
public static List<String> getAuthorityInfoAccesssUrls(String text)
{
List<String> containedUrls = new ArrayList<String>();
Pattern pattern = Pattern.compile(
"(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)"
+ "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*"
+ "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~#!:/{};']*)",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
Matcher urlMatcher = pattern.matcher(text);
while (urlMatcher.find())
{
containedUrls.add(text.substring(urlMatcher.start(0),
urlMatcher.end(0)));
}
return containedUrls;
};
public static void main(String[] args) {
readCert rc = new readCert();
try {
File file = new File("yourcert.crt");
byte[] encCert = new byte[(int) file.length()];
FileInputStream fis = new FileInputStream(file);
fis.read(encCert);
fis.close();
InputStream in = new ByteArrayInputStream(encCert);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)certFactory.generateCertificate(in);
X509CertImpl impl = (X509CertImpl)cert;
int extnum = 0;
if (cert.getNonCriticalExtensionOIDs() != null) {
for (String extOID : cert.getNonCriticalExtensionOIDs()) {
Extension ext = impl.getExtension(new ObjectIdentifier(extOID));
if (ext != null) {
if (rc.isExtAuthorityInfoAccess(ext)) {
System.out.println(rc.getAuthorityInfoAccesssUrls(ext.toString()));
// System.out.println("#"+(++extnum)+": "+ ext.toString());
// CA ISSUERS ARE HERE
}
}
}
}
} catch ( Exception e) {
e.printStackTrace();
};
}
}
I'm required to create a program that validate any given X509 Certificate Version3 if it is trusted or not by verifying the signature. In order to to do that, I need to be able to know the issuer's public key who has signed that given certificate. I know how to output the issuer information using getIssuerDN() method but what I don't know is how to get the issuer's public key when I only know its name !
The only solution I have so far is to maintain a list of public keys for the most common Certificate Authorities on the web, and just search through it. Although this solution is doable, it seems impractical to me.
Therefore, is there another idea to get the Issuer's public key from a certificate directly and then complete the process of verification ?
Here is my code for getting the Issuer's name but NOT its public key.
URL url = new URL("https link here!");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.connect();
Certificate cert[] = con.getServerCertificates();
X509Certificate x509cert = (X509Certificate) cert[0];
String[] st= x509cert.getIssuerDN().toString().split(",");
System.out.println("Issuer CN is: "+st[0].toString());
I'm required to create a program that validate any given X509 Certificate Version3 if it is trusted or not by verifying the signature. In order to to do that, I need to be able to know the issuer's public key who has signed that given certificate.
No. Either you or whoever gave you the assignment doesn't understand PKI. The idea is that you already trust some issuers by virtue of already having their certificates, e.g. in the cacerts file distributed with the JRE, and therefore their public keys. If the certificate you're asked to verify is traceable to one of these issuers and it passes all other verifications, you can trust it. Mere verification via the public key alone is not sufficient.