diff options
Diffstat (limited to 'base/console/src/com/netscape/admin/certsrv/connection/JSSConnection.java')
-rw-r--r-- | base/console/src/com/netscape/admin/certsrv/connection/JSSConnection.java | 761 |
1 files changed, 761 insertions, 0 deletions
diff --git a/base/console/src/com/netscape/admin/certsrv/connection/JSSConnection.java b/base/console/src/com/netscape/admin/certsrv/connection/JSSConnection.java new file mode 100644 index 000000000..27292b3d9 --- /dev/null +++ b/base/console/src/com/netscape/admin/certsrv/connection/JSSConnection.java @@ -0,0 +1,761 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.admin.certsrv.connection; + +import java.util.*; +import java.net.*; +import java.io.*; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import com.netscape.admin.certsrv.*; +import com.netscape.certsrv.common.*; +import com.netscape.management.client.util.Debug; +import com.netscape.management.client.util.*; +import org.mozilla.jss.ssl.*; +import org.mozilla.jss.*; +import org.mozilla.jss.util.*; +import org.mozilla.jss.crypto.*; +import org.mozilla.jss.pkcs11.*; +import javax.swing.*; +import java.awt.*; + +/** + * JSSConnection deals with establishing a connection to + * a server, sending requests and reading responses. + * + * XXX - Performance optimizations if any, persistent connection + * support, server auth verification and client authentication + * support to be added. NEED TO COME BACK AND CLEAN UP - coding + * standard. + * + * @author Jack Pan-Chen + * @author kanda + * @author Christine Ho + * @version $Revision$, $Date$ + */ +public class JSSConnection implements IConnection, SSLCertificateApprovalCallback, + SSLClientCertificateSelectionCallback { + + /*========================================================== + * variables + *==========================================================*/ + + /* static variables */ + static CryptoManager cryptoManager; + static CertificateFactory cf; + static SelectCertDialog selectCertDialog = null; + static PromptForTrustDialog promptForTrustDialog = null; + + /* private valiable */ + private InputStream httpIn; + private OutputStream httpOut; + private byte[] body; + private int bodyLen; + private String header; + private int available; + private int totalRead; + private boolean endOfHeader = false; + + private static int HTTP_OK_RESPONSE = 200; + private static final String PANELNAME = "SSLCLIENT"; + private boolean abort = false;; + private boolean mClientAuth = false; + private boolean mCertAccepted = true; + private boolean mClientCertFound = true; + private boolean mServerCertImported = true; + private boolean mTokenPasswordInit = true; + private boolean mTokenPasswdSame = true; + + protected SSLSocket s = null; + + /*========================================================== + * constructors + *==========================================================*/ + public JSSConnection(String host, int port) + throws IOException, UnknownHostException { + + UtilConsoleGlobals.initJSS(); + cf = UtilConsoleGlobals.getX509CertificateFactory(); + try { + cryptoManager = CryptoManager.getInstance(); + } catch (Exception e) { + } + + // SSLSocket needs to be set before getting an instance + // to get the ciphers + SSLSocket.enableSSL2Default(false); + SSLSocket.enableSSL3Default(true); + int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005; + int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A; + + int ciphers[] = SSLSocket.getImplementedCipherSuites(); + for (int i = 0; ciphers != null && i < ciphers.length; i++) { + // make sure SSLv2 ciphers are not enabled + if ((ciphers[i] & 0xfff0) !=0xff00) { + Debug.println("JSSConnection Debug: non-SSL2 NSS Cipher Supported '0x" + + Integer.toHexString(ciphers[i]) + "'"); + SSLSocket.setCipherPreferenceDefault(ciphers[i], true); + } else { + Debug.println("JSSConnection Debug: SSL2 (turned off) NSS Cipher Supported '0x" + + Integer.toHexString(ciphers[i]) + "'"); + SSLSocket.setCipherPreferenceDefault(ciphers[i], false); + } + + /* Enable ECC Cipher */ + + if (ciphers[i] == TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) { + Debug.println("JSSConnection Debug: found TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, setting preference"); + SSLSocket.setCipherPreferenceDefault(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, true); + } + if (ciphers[i] == TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) { + Debug.println("JSSConnection Debug: found TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, setting preference"); + SSLSocket.setCipherPreferenceDefault(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, true); + } + } + s = new SSLSocket(host, port, null, 0, this, this); + s.enableSSL2(false); + s.enableSSL2Default(false); + s.enableV2CompatibleHello(false); + s.enableSSL3(true); + s.enableSSL3Default(true); + + // Initialze Http Input and Output Streams + httpIn = s.getInputStream(); + httpOut = s.getOutputStream(); + cryptoManager.setPasswordCallback(new pwcb()); + Debug.println("JSSConnection Debug: end of JSSConnection constructor"); + } + + public boolean approve(org.mozilla.jss.crypto.X509Certificate serverCert, + ValidityStatus status) + { + if (!mCertAccepted) + return false; + + boolean promptForTrust = true; + + //if server auth is not enabled + if (!(UtilConsoleGlobals.isServerAuthEnabled())) { + return mCertAccepted; + } + + Enumeration errors = status.getReasons(); + //if there are more then 1 error we need to propmt user for trust + promptForTrust = errors.hasMoreElements(); + + /* if trusted already */ + if (!promptForTrust) + return mCertAccepted; + + //the x509certificate pass in by jss is lacking some + //api. so I am getting the encoding then + //use the default security provider provided by sun + //to decode certificate. + //due to the fact that current JSS(version2.1) will clobber + //the way jdk loads the default sun security provider I am + //using the workaround for now. Which is to load the sun + //provider before jss is loaded. (see static section above) + X509Certificate x509Cert = null; + try { + ByteArrayInputStream bais = new ByteArrayInputStream(serverCert.getEncoded()); + + while (bais.available() > 0) { + x509Cert = (X509Certificate)(cf.generateCertificate(bais)); + Debug.println(x509Cert.toString()); + } + } catch (Exception e) { + e.printStackTrace(); + } + + //bring up the trust dialog + promptForTrustDialog = new PromptForTrustDialog(getFrame(), x509Cert, status); + promptForTrustDialog.setVisible(true); + mCertAccepted = promptForTrustDialog.isCertAccepted(); + if (mCertAccepted) { + //user want to save this certificate not just this session + //so we have to store the cert as perm cert. + if (!(promptForTrustDialog.isAcceptedForOneSession())) { + try { + String nickname = serverCert.getNickname(); + + CryptoToken internalToken = + cryptoManager.getInternalKeyStorageToken(); + + if (!internalToken.passwordIsInitialized()) { + InitPasswordDialog initPasswordDialog = + new InitPasswordDialog(internalToken); + initPasswordDialog.setVisible(true); + if (initPasswordDialog.isCancel()) { + mTokenPasswordInit = false; + return false; + } + if (!initPasswordDialog.isPwdSame()) { + mTokenPasswdSame = false; + mTokenPasswordInit = false; + return false; + } + if (!initPasswordDialog.isTokenInit()) { + mTokenPasswordInit = false; + return false; + } + } + + if (!internalToken.isLoggedIn()) { + internalToken.login(new pwcb()); + } + if (abort) { + mServerCertImported = false; + mCertAccepted = false; + return false; + } + InternalCertificate internalCert = + cryptoManager.importCertToPerm(serverCert, + (nickname==null)?serverCert.getSubjectDN().toString():nickname); + internalCert.setSSLTrust( + org.mozilla.jss.crypto.InternalCertificate.TRUSTED_PEER | + org.mozilla.jss.crypto.InternalCertificate.VALID_PEER); + } catch (Exception e) { + mServerCertImported = false; + mCertAccepted = false; + if (Debug.getTrace()) { + e.printStackTrace(); + } + return false; + } + } + } + + return mCertAccepted; + } + + public boolean isSamePwd() { + return mTokenPasswdSame; + } + + public boolean isTokenPasswordInit() { + return mTokenPasswordInit; + } + + public boolean hasClientCert() { + return mClientCertFound; + } + + public boolean isClientAuth() { + return mClientAuth; + } + + public boolean isCertAccepted() { + return mCertAccepted; + } + + public boolean isAbortAction() { + return abort; + } + + public boolean isServerCertImported() { + return mServerCertImported; + } + + public String select(Vector nicknames) + { + selectCertDialog = null; + mClientAuth = true; + if (nicknames == null || nicknames.size() == 0) { + mClientCertFound = false; + return ""; + } + + selectCertDialog = new JSSConnection.SelectCertDialog(); + + Debug.println("JSSConnection::select(...) - SELECT CERTIFICATE"); + selectCertDialog.setCertList(nicknames); + selectCertDialog.setVisible(true); + return (selectCertDialog.isCancel()?"":selectCertDialog.getSelectedCert()); + } + + public class pwcb implements PasswordCallback { + private int nthPrompt = 0; + private static final int MAX_PASSWORD_PROMPT = 20; + GetPasswordDialog getPasswordDialog = null; + + public Password getPasswordFirstAttempt(PasswordCallbackInfo info) + throws PasswordCallback.GiveUpException { + + if (abort) + throw new PasswordCallback.GiveUpException(); + + nthPrompt++; + + if (getPasswordDialog == null) + getPasswordDialog = new GetPasswordDialog(); + + getPasswordDialog.setPasswordInfo(info, false); + getPasswordDialog.setVisible(true); + + if (getPasswordDialog.isCancel()) { + nthPrompt = 0; + abort = true; + throw new PasswordCallback.GiveUpException(); + } + + return getPasswordDialog.getPassword(); + } + + public Password getPasswordAgain(PasswordCallbackInfo info) + throws GiveUpException + { + + if (abort) + throw new PasswordCallback.GiveUpException(); + nthPrompt++; + if (nthPrompt > MAX_PASSWORD_PROMPT || getPasswordDialog.isCancel()) { + nthPrompt = 0; + abort = true; + throw new PasswordCallback.GiveUpException(); + } + + getPasswordDialog.setPasswordInfo(info, true); + getPasswordDialog.setVisible(true); + + return getPasswordDialog.getPassword(); + } + } + + /*========================================================== + * public methods + *==========================================================*/ + + /** + * Send request to the server using this connection + * + * @param req request object + * @return status 1-success, 0- failed + * @excpetion IOExcpetion + */ + public int sendRequest(String req) + throws IOException { + + int stat = 1; + if (req == null) + { + //System.out.println("Request is null"); + return 0; + } + endOfHeader = false; + + PrintStream ps = new PrintStream(httpOut); + ps.println(req); + ps.println(); + ps.flush(); + try + { + Thread.sleep(100); + } + catch (Exception e) { + Debug.println("JSSConnection Debug: in sendRequest:"+e.toString()); + System.out.println("sleeping "+e.toString()); + } + //System.out.println("Request Sent - bytes:" + httpOut.getTotal()); + + // Init the Reply stream + totalRead = 0; + header = null; + initReadResponse(); + return stat; + } + + /** + * Retrieve the input stream + */ + public InputStream getInputStream() + throws IOException { + + return s.getInputStream(); + } + + /** + * Read + */ + public int read(byte[] buf) + throws IOException { + + return httpIn.read(buf, 0, buf.length); + } + + /** + * Get Header + */ + public String getHeader() { + if (header == null) + return "No Header Read"; + else + return header; + } + + /** + * Get response + */ + public byte[] getResponse() { + if (totalRead == 0) + return null; + else { + byte[] buf = new byte[bodyLen]; + System.arraycopy(body, 0, buf, 0, bodyLen); + return buf; + } + } + + /** + * get available + */ + public int available() + throws IOException { + + return httpIn.available(); + } + + /** + * Disconnect this connection + */ + public void disconnect() { + try { + s.close(); + } catch (Exception e) { + //ignor ? + } + } + + /** + * Set time out + */ + public void setSoTimeout(int timeout) throws SocketException { + //System.out.println("JSSConnection: setSoTimeout() - "+timeout); + s.setSoTimeout(timeout); + } + + /*========================================================== + * private methods + *==========================================================*/ + + private JFrame getFrame() { + if (UtilConsoleGlobals.getActivatedFrame() != null) + return UtilConsoleGlobals.getActivatedFrame(); + return new JFrame(); + } + + private void initReadResponse() + throws IOException { + + readHeader(); + readBody(); + } + + private int readLineFromStream(InputStream is, byte line[], + int startpos, int len) throws IOException { + //return is.readLine(line, startpos, len); + int pos = startpos; + int count = 0; + while (len > 0) + { + int nRead = httpIn.read(line, pos, 1); + if (nRead == -1) + break; + count++; + if (line[pos] == '\n') { + break; + } + pos++; + } + return count > 0 ? count : -1; + } + + private void readHeader() throws IOException + { + // Read the status line of response and parse for + // Errors. + byte[] headerLine = new byte[1096]; + int nRead = readLineFromStream(httpIn, headerLine, 0, 1096); + + //System.out.println("XXX read " + nRead); + + if (requestFailed(new String(headerLine))) { + Debug.println("JSSConnection Debug: in readHeader requestFailed"); + throw new IOException(getReasonPhrase(new String (headerLine))); + } + + while (true) { + nRead = readLineFromStream(httpIn, headerLine, 0, 1096); + int available = httpIn.available(); + + //System.out.println("Available: " + available); + + if (nRead == -1) { + System.out.println("Unexpected end of stream"); + break; + } + + processHeader(headerLine, nRead); + + if (endOfHeader) { + //System.out.println("End of Header"); + break; + } else { + //System.out.println("Header: " + new String(headerLine) + // + ", nRead: " + nRead); + } + } + } + + private boolean endOfHeader(byte[] hdr, int available) { + if (available == 2) { + int c1 = (int)hdr[0]; + int c2 = (int)hdr[1]; + + //System.out.println("C1= " + c1); + //System.out.println("C2= " + c2); + + return true; + } else + return false; + } + + private void readBody() + throws IOException { + + body = new byte[bodyLen]; + totalRead = 0; + while (totalRead < bodyLen) { + int nRead = httpIn.read(body, totalRead, bodyLen - totalRead); + totalRead += nRead; + } + } + + + private void processHeader(byte[] buf, int nRead) + { + if (endOfHeader(buf, nRead)) { + endOfHeader = true; + return; + } + + String hdr = new String(buf, 0, nRead); + int index = 0; + if (hdr.toLowerCase().startsWith("content-length: ")) { + try { + String length = hdr.substring(hdr.indexOf(": ") + 1); + bodyLen = Integer.parseInt(length.trim()); + return; + } catch (Exception e){e.printStackTrace(); } + } + } + + private boolean requestFailed(String header) { + return (header.indexOf(Integer.toString(HTTP_OK_RESPONSE)) > 0) ? false: true; + } + + private String getReasonPhrase(String header) { + String str1 = header.substring(header.indexOf(' ') +1); + return str1.substring(str1.indexOf(' ') +1); + } + + class InitPasswordDialog extends AbstractDialog { + protected ResourceBundle mResource = + ResourceBundle.getBundle(CMSAdminResources.class.getName()); + SingleBytePasswordField pwd; + SingleBytePasswordField pwdAgain; + CryptoToken mToken; + boolean tokenPasswdInit = true; + boolean pwdSame = true; + + public InitPasswordDialog(CryptoToken token) { + super(null,"",true, OK|CANCEL); + setMinimumSize(300, 150); + mToken = token; + setTitle(mResource.getString("SSLCLIENT_INITPASSWORD_DIALOG_TITLE")); + Container p = getContentPane(); + p.setLayout(new GridBagLayout()); + + int y = 0; + pwd = new SingleBytePasswordField(); + pwdAgain = new SingleBytePasswordField(); + JLabel pwdLbl = new JLabel(); + JLabel pwdAgainLbl = new JLabel(); + pwdLbl.setText(mResource.getString("SSLCLIENT_INITPASSWORD_PWD_LABEL")); + pwdAgainLbl.setText( + mResource.getString("SSLCLIENT_INITPASSWORD_PWDAGAIN_LABEL")); + GridBagUtil.constrain(p, pwdLbl, + 0, y, 1, 1, + 0.0, 0.0, + GridBagConstraints.EAST, GridBagConstraints.NONE); + GridBagUtil.constrain(p, pwd, + 1, y, GridBagConstraints.REMAINDER, 1, + 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL); + GridBagUtil.constrain(p, pwdAgainLbl, + 0, ++y, 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.NONE); + GridBagUtil.constrain(p, pwdAgain, + 1, y, GridBagConstraints.REMAINDER, 1, + 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL); +/* + GridBagUtil.constrain(p, pwd, + 0, ++y, 1, 1, + 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + 0, 0, 0, 0); +*/ + + pack(); + } + + protected void okInvoked() { + if (!pwd.getText().equals(pwdAgain.getText())) { + pwdSame = false; + dispose(); + return; + } + + try { + mToken.initPassword(null, getPassword()); + dispose(); + } catch (Exception e) { + tokenPasswdInit = false; + } + } + + public boolean isPwdSame() { + return pwdSame; + } + + public boolean isTokenInit() { + return tokenPasswdInit; + } + + public void setVisible(boolean visible) { + pack(); + pwd.grabFocus(); + super.setVisible(visible); + } + + public Password getPassword() { + Password jssPwd = new Password(pwd.getText().toCharArray()); + return jssPwd; + } + } + + class GetPasswordDialog extends AbstractDialog { + + MultilineLabel enterPwdLabel = new MultilineLabel(); + protected ResourceBundle mResource = + ResourceBundle.getBundle(CMSAdminResources.class.getName()); + SingleBytePasswordField pwd; + public GetPasswordDialog() { + super(null,"",true, OK|CANCEL); + setTitle(mResource.getString("SSLCLIENT_PASSWORD_DIALOG_TITLE")); + Container p = getContentPane(); + p.setLayout(new GridBagLayout()); + + int y = 0; + GridBagUtil.constrain(p, enterPwdLabel, + 0, y, 1, 1, + 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + 0, 0, 0, 0); + + pwd = new SingleBytePasswordField(); + GridBagUtil.constrain(p, pwd, + 0, ++y, 1, 1, + 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + 0, 0, 0, 0); + + pack(); + } + + public void setVisible(boolean visible) { + pack(); + pwd.grabFocus(); + super.setVisible(visible); + } + + public void setPasswordInfo(PasswordCallbackInfo info, boolean getPwdAgain) { + if (getPwdAgain) + enterPwdLabel.setText(mResource.getString( + "SSLCLIENT_PASSWORDAGAIN_DIALOG_LABEL")+" "+info.getName()+":"); + else + enterPwdLabel.setText(mResource.getString( + "SSLCLIENT_PASSWORD_DIALOG_LABEL")+" "+ info.getName()+":"); + Debug.println(info.getName()); + } + + public Password getPassword() { + Password jssPwd = new Password(pwd.getText().toCharArray()); + return jssPwd; + } + } + + + class SelectCertDialog extends AbstractDialog { + + JComboBox certList = new JComboBox(); + protected ResourceBundle mResource = ResourceBundle.getBundle( + CMSAdminResources.class.getName()); + public SelectCertDialog() { + super(null,"", true, OK|CANCEL); + setTitle(mResource.getString("SSLCLIENT_CERTSELECT_DIALOG_TITLE")); + + Container p = getContentPane(); + p.setLayout(new GridBagLayout()); + + int y = 0; + GridBagUtil.constrain(p, new JLabel( + mResource.getString("SSLCLIENT_CERTSELECT_DIALOG_LABEL")), + 0, y, 1, 1, + 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + 0, 0, 0, 0); + + GridBagUtil.constrain(p, certList, + 0, ++y, 1, 1, + 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + 0, 0, 0, 0); + pack(); + } + + public void setCertList(Vector nicknames) { + certList.removeAllItems(); + Enumeration enum1 = nicknames.elements(); + while (enum1.hasMoreElements()) { + certList.insertItemAt(enum1.nextElement(), 0); + } + try { + certList.setSelectedIndex(0); + } catch (Exception e) { + } + } + + public String getSelectedCert() { + return certList.getSelectedItem().toString(); + } + + } + +} |