SSL a Java

Pozadi

Java ma zmet divne definovanych terminu jako JCE,JSSE,JCA,Java Security atd. Tohle je muj vyklad co znamenaji:

Java Security - obecny pojem - zahrnuje Security Architecture a Cryptography Architecture
Java Security Architecture - ochrana pristupu (permissions,policy) ke zdrojum - soucast JRE
JCA - Java Cryptography Architecture - X509v3 certifikaty, digitalni podpisy, message digesty - soucast JRE
JCE - Java Cryptography Extension - pridava moznost sifrovani
JSSE - Java Secure Socket Extension - pridava SSL (Socket a https:)
JAAS - Java Autentication and Autorization Service - pridava autentizaci uzivatele
JCE,JSSE a JAAS byly ve verzich JDK 1.2,1.3 tzv. "optional packages", posleze byly integrovany primo do JDK 1.4.
Dále byla do JDK1.4 přidána Java implementace GSS-API (General Security Services API) definované v RFC 2078, která podporuje autentizaci pomocí Kerberos v5.

Implementace

ISNetworks S/MIME Provider for Java - umi sifrovat a dig. podepisovat maily
BeeCrypt - JCE compatible Cryptographic Service Provider
BeeJCE - LGPL implementace JCE
BouncyCastle - implementace JCE/JCA, S/MIME a dalsiho, s volnou licenci

Vysvetleni pojmu (prevzato z JavaCA)

PKCS

The acronym PKCS is used a lot in cryptography. It stands for Public Key Cryptography Standards. These standards are issued by RSA and can be found on the web at http://www.rsasecurity.com/rsalabs/pkcs/. These standards include:

ASN.1, DER and PEM

ASN.1 is a standard for describing data in a portable fashion so that it can be exchanged by computer systems. It is used by all kinds of things from LDAP to cell phones. ASN.1 is used in cryptography for describing keys. DER is a standard for encoding ASN.1 data into a stream of bytes. PEM (RFCs 1421-1424) defined a way to take a stream of bytes and make an ASCII text message out of them. Essentially PEM says you Base64 (RFC 2045, section 6.8) encode the DER encoded data and then wrap it with some header and footer lines.

Key File Formats

Certificates by themselves are easy, they are pretty much always stored in a PEM encoded file.

Private keys are a bit more confusing. There are three common formats. The first is OpenSSL's "traditional" format. OpenSSL defaults to this format and it is what you get if you've followed most of the "How to be your own CA" documents on the web.

The second format is PKCS#8. OpenSSL can transparently read either traditional or PKCS#8 keys, and recommends PKCS#8 format in all of their documentation.

The third format is PKCS#12, which generally is used for distributing a key and certificate to a user. Web browsers in particular support this format for reading in personal certificates. JavaCA supports writing new certificates out in PKCS#12.

Tipy

Jak dostat verejny klic s certifikaty ve forme X509 z JKS KeyStore ven ? Takhle:
javac ExportPublicKey.java
#keytool -export -rfc -alias klic_alias -keystore muj.ks -file cert.pem
#exportuje jen prvni certifikat v retezci

Jak dostat tajny klic z JKS KeyStore ven ? Takhle:

javac ExportPrivateKey.java
#KS -> PKCS8 DER
java ExportPrivateKey muj.ks klic_alias heslo >klic_pkcs8_nekrypt.der
#PKCS8 DER -> SSLeay PEM
openssl pkcs8 -inform DER -nocrypt -in klic_pkcs8_nekrypt.der -out klic_nekryp.pem
#pridat heslo
openssl rsa -in klic_nekrypt.pem -des3 -out klic.pem

Jak dostat tajny klic a certifikaty z Netscape ? Takhle:
#vyexportovat z Netscape jako PKCS12 soubor.p12
openssl pkcs12 -in soubor.p12 -nocerts
openssl pkcs12 -in soubor.p12 -nokeys

Jak pouzivat certifikat z Netscape v Jave ?
Je nutne nainstalovat JSSE, ktere ma podporu PKCS12 KeyStore, a pak proste pouzivat soubor vyexportovany z Netscape jako KeyStore.

Příklad podepsaného a zašifrovaného S/MIME emailu pomocí BouncyCastle

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;

import java.security.Security;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertStore;
import java.security.cert.CollectionCertStoreParameters;

import java.util.Date;
import java.util.Arrays;
import java.io.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
import javax.mail.Message.RecipientType;
import javax.activation.MailcapCommandMap;
import javax.activation.CommandMap;

public class SMIME {

static final String BOUNCYCASTLE = "BC";

public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());

String ksfile = args[0];
String alias = args[1];
char [] password = args[2].toCharArray();
char [] password2 = args[2].toCharArray();

String kstype = null;
if(ksfile.substring(ksfile.length()-3).equals(".ks")) kstype = "JKS";
if(ksfile.substring(ksfile.length()-4).equals(".p12")) kstype = "PKCS12";
if(kstype==null) {
System.err.println("keystore file name must end with .ks or .p12");
return;
}

//nacti keystore
KeyStore store = KeyStore.getInstance(kstype);
store.load(new FileInputStream(ksfile), password);

//vytahni klic a certifikat
PrivateKey privateKey = (PrivateKey) store.getKey(alias, password2);
System.out.println("PrivateKey: "+privateKey.getAlgorithm());
Certificate[] chain = store.getCertificateChain(alias);
X509Certificate cert = (X509Certificate) chain[0];
System.out.println("X509: "+cert.getSubjectX500Principal().getName());
//podepisovac
SMIMESignedGenerator podepisovac = new SMIMESignedGenerator();
podepisovac.addSigner(privateKey,cert, "DSA".equals(privateKey.getAlgorithm()) ?
SMIMESignedGenerator.DIGEST_SHA1 : SMIMESignedGenerator.DIGEST_MD5);
podepisovac.addCertificatesAndCRLs(CertStore.getInstance("Collection",new CollectionCertStoreParameters(Arrays.asList(chain))));

//vyrob cast k podepsani
MimeBodyPart mbp1 = new MimeBodyPart();
mbp1.setContent("Tohle je vyrobeno pomoci JavaMail 1.3.3 a BouncyCastle 1.30. \n\nMakub","text/plain; charset=ISO-8859-2");
MimeBodyPart mbp2 = new MimeBodyPart();
String soubor = "SMIME.java";
mbp2.setDataHandler(new DataHandler(new FileDataSource(soubor)));
mbp2.setFileName(soubor);
MimeMultipart mixed = new MimeMultipart("mixed");
mixed.addBodyPart(mbp1);
mixed.addBodyPart(mbp2);

MimeBodyPart mixedBody = new MimeBodyPart();
mixedBody.setContent(mixed);

//podepis
MimeMultipart podepsane = podepisovac.generate(mixedBody,BOUNCYCASTLE);

MimeBodyPart podepsaneBody = new MimeBodyPart();
podepsaneBody.setContent(podepsane);

//zakryptuj
SMIMEEnvelopedGenerator sifrovac = new SMIMEEnvelopedGenerator();
sifrovac.addKeyTransRecipient(cert);

//MimeBodyPart zasifrBody = sifrovac.generate(podepsaneBody, SMIMEEnvelopedGenerator.RC2_CBC,BOUNCYCASTLE);
MimeBodyPart zasifrBody = sifrovac.generate(podepsaneBody, SMIMEEnvelopedGenerator.AES256_CBC,BOUNCYCASTLE);

//sestavit email
Session session = Session.getDefaultInstance(System.getProperties());
MimeMessage msg = new MimeMessage(session);
//From: header
InternetAddress from = new InternetAddress( "makub@ics.muni.cz", "Martin Kuba", "UTF-8");
msg.setFrom(from);
//To: header
msg.setRecipient(RecipientType.TO,new InternetAddress( "makub@ics.muni.cz", "Makub", "UTF-8"));
//Subject: header
msg.setSubject("S/MIME podpis+sifra z Javy","ISO-8859-2");
//Body
msg.setContent(zasifrBody.getContent(), zasifrBody.getContentType());
msg.setSentDate(new Date());
msg.saveChanges();

//poslat
Transport.send(msg);
}
}

Příklad použití Kerberos v5

Overeni hesla vuci KDC:
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import javax.security.auth.login.Configuration;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.util.HashMap;
import java.io.IOException;

public class OverHeslo {

static final String MODULNAME = "OvereniHesla";

/**
* Trida misto .conf souboru.
*/
static class MyConfiguration extends Configuration {
public void refresh() { }
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
if(!MODULNAME.equals(name)) { throw new RuntimeException("Cekal jsem "+MODULNAME+" a ne "+name); }
HashMap opts = new HashMap(3);
opts.put("useTicketCache","false");
opts.put("doNotPrompt","false");
return new AppConfigurationEntry[] {
new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",LoginModuleControlFlag.REQUIRED,opts)
};
}
}

static class MyCallback implements CallbackHandler {
String jmeno;
char[] heslo;
MyCallback(String jmeno, String heslo) {
this.jmeno = jmeno;
this.heslo = heslo.toCharArray();
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
NameCallback nc = (NameCallback)callbacks[i];
nc.setName(jmeno);
} else if (callbacks[i] instanceof PasswordCallback) {
PasswordCallback pc = (PasswordCallback)callbacks[i];
pc.setPassword(heslo);
} else {
System.err.println("Callback: "+callbacks[i].getClass().getName());
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
}
public static void main(String[] args) {
String username = args[0];
String heslo = args[1];

LoginContext lc = null;
try {
lc = new LoginContext(MODULNAME, null, new MyCallback(username,heslo), new MyConfiguration());
lc.login();
} catch (LoginException ex) { ex.printStackTrace(); return; }
System.out.println("Overeno !");
}
}

Zavolani kodu s pravy uzivatele overeneho Kerberem

import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.Subject;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import com.sun.security.auth.callback.TextCallbackHandler;

/**
* Ukazuje moznosti JAAS autentizace pomoci Kerberos V5. Vyzaduje JDK1.4.
* Spust jako <pre>
* java \
* -Djava.security.manager \
* -Djava.security.policy=MujLogin.policy \
* -Djava.security.auth.login.config=MujLogin.conf \
* -Dsun.security.krb5.debug=true \
* MujLogin
* </pre> kde <code>MujLogin.policy</code> musi obsahovat:
* <pre>
* grant codeBase "file:./*" {
* permission javax.security.auth.AuthPermission "createLoginContext.Kerber";
* permission javax.security.auth.AuthPermission "doAsPrivileged";
* };
* </pre> coz povoluje vytvoreni LoginContextu a spusteni kodu s kontrolou prav;
* a <code>MujLogin.conf</code> musi obsahovat:
* <pre>
* Kerber {
* com.sun.security.auth.module.Krb5LoginModule required useTicketCache=true doNotPrompt=false;
* };
* </pre> coz rika ze se pouzije autentizacni modul pro Kerberos v5, ktery
* se pokusi ziskat listky z cache a pokud se to nepovede, rekne si o ne.<P>
* @author Makub
*/
public class MujLogin {
public static void main(String[] args) {
/*
v souboru urcenem -Djava.security.auth.login.config= najde
radek urcujici jaky login modul se pouzije, napr.
Kerber { com.sun.security.auth.module.Krb5LoginModule required useTicketCache=true doNotPrompt=false ; };
*/
String moduleConfName = "Kerber" ;
//TextCallbackHandler umozni zeptat se uzivatele na heslo, pokud je to nutne
LoginContext lc = null;
try {
lc = new LoginContext(moduleConfName, new TextCallbackHandler());
lc.login();
System.out.println(lc.getSubject().toString());
} catch (LoginException ex) { ex.printStackTrace(); return; }

//jsme zalogovani, delej neco
try {
Object o = Subject.doAsPrivileged(lc.getSubject(), new KontrolovanyKod(), null);
} catch (PrivilegedActionException pae) { pae.printStackTrace(); return; }

}
}

/**
* Metoda KontrolovanyKod.run() bude spustena s pravy Subjectu.
*/
class KontrolovanyKod implements PrivilegedExceptionAction {

public KontrolovanyKod() { }
public Object run() throws Exception {
System.out.println("KontrolovanyKod bezi");
return null;
}
}