/*************************************************************************
*
* $RCSfile: PGP263x.java,v $
*
* $Revision: 1.1.1.1 $
*
* last change: $Author: hr $ $Date: 2000/09/18 16:16:50 $
*
* The Contents of this file are made available subject to the terms of
* either of the following licenses
*
* - GNU Lesser General Public License Version 2.1
* - Sun Industry Standards Source License Version 1.1
*
* Sun Microsystems Inc., October, 2000
*
* GNU Lesser General Public License Version 2.1
* =============================================
* Copyright 2000 by Sun Microsystems, Inc.
* 901 San Antonio Road, Palo Alto, CA 94303, USA
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1, as published by the Free Software Foundation.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*
*
* Sun Industry Standards Source License Version 1.1
* =================================================
* The contents of this file are subject to the Sun Industry Standards
* Source License Version 1.1 (the "License"); You may not use this file
* except in compliance with the License. You may obtain a copy of the
* License at http://www.openoffice.org/license.html.
*
* Software provided under this License is provided on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
* WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
* MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
* See the License for the specific provisions governing your rights and
* obligations concerning the Software.
*
* The Initial Developer of the Original Code is: Sun Microsystems, Inc.
*
* Copyright: 2000 by Sun Microsystems, Inc.
*
* All Rights Reserved.
*
* Contributor(s): _______________________________________
*
*
************************************************************************/
package com.sun.star.pgp.version;
import com.sun.star.io.XInputStream;
import com.sun.star.io.XOutputStream;
import com.sun.star.io.IOException;
import com.sun.star.pgp.SignatureInfo;
import com.sun.star.pgp.UserInfo;
import com.sun.star.pgp.IPGP;
import com.sun.star.pgp.PGPException;
import com.sun.star.pgp.UserInfo;
import com.sun.star.pgp.ui.IPassPhraseDialog;
import java.io.*;
import java.util.*;
//==================================================================================================
public class PGP263x implements
IPGP
{
static final boolean DEBUG = false;
private boolean _bIsAvailable = false;
private UserInfo[] _publicUsers = null;
private UserInfo[] _signatureUsers = null;
private UserInfo _defaultUser = null;
private String _name = null;
//______________________________________________________________________________________________
private boolean getUserInfos( InputStream input ) throws java.io.IOException
{
String str;
BufferedReader in = new BufferedReader( new InputStreamReader( input ) );
// skip further two lines
for ( int i = 2; i-- >= 0; )
{
if ((str = in.readLine()) == null)
return false;
}
Vector lines = new Vector();
while ((str = in.readLine()) != null)
{
lines.addElement( str );
}
// skip last one
int nLen = lines.size() -1;
if (nLen <= 0)
return false;
if (DEBUG)
System.out.println( "# len="+nLen );
// rows appear pubUser0, sigUser0, ...:/usr/local/jl/564a4/program/classes/classes.jar:/usr/local/jl/564a4/program/classes/one.jar:/usr/local/jl/564a4/program/classes/pgp.jar:/usr/local/jl/564a4/program/classes/sandbox.jar:/usr/local/jl/564a4/program/classes/swingall.jar:/usr/local/jl/564a4/program/classes/tkt.jar:/usr/local/jl/564a4/program/classes/uno.jar:/usr/local/jl/564a4/program/classes/usr.jar:/develop4/update/SRC564/564/tkt/unxsols2.pro/class:/develop4/update/SRC564/564/uno/unxsols2.pro/class:/develop4/update/SRC564/564/usr/unxsols2.pro/class:/develop4/update/SRC564/564/jlibs/unxsols2.pro/class:/usr/local/jl/src564/extensions/unxsols2.pro/class
Vector pub = new Vector();
Vector sig = new Vector();
UserInfo lastPublic = null;
for ( Enumeration enum = lines.elements(); nLen-- > 0; )
{
// type, bits/keyid, date, name <email>
str = (String)enum.nextElement();
boolean bPub = str.indexOf( "pub" ) == 0;
UserInfo info = new UserInfo(); StringBuffer buf;
char c;
String keyId = null;
// skip type
int nStrPos = 0, nStrLen = str.length();
while (nStrPos < nStrLen && str.charAt( nStrPos ) != ' ')
++nStrPos;
while (nStrPos < nStrLen && str.charAt( nStrPos ) == ' ')
++nStrPos;
if (bPub)
{
// read bits new JXGridBagConstraints( 0, 0, 2, 1, 1.0, 0.0,
buf = new StringBuffer();
for ( ; nStrPos < nStrLen && (c = str.charAt( nStrPos )) != '/'; ++nStrPos )
buf.append( c );
info._bits = buf.toString();
// skip '/'
if (nStrPos < nStrLen)
++nStrPos;
}
// read keyId
buf = new StringBuffer();
for ( ; nStrPos < nStrLen && (c = str.charAt( nStrPos )) != ' '; ++nStrPos )
buf.append( c );
info._key = buf.toString();
while (nStrPos < nStrLen && str.charAt( nStrPos ) == ' ')
++nStrPos;
// if we read a sig and last public key is same user (with more info, e.g. bits)
// then shortcut with info of this user
if (!bPub &&
lastPublic != null && lastPublic._key.equals( info._key ))
{
info = lastPublic;
}
else // else read rest of string
{
if (bPub)
{
// read date
buf = new StringBuffer();
for ( ; nStrPos < nStrLen && (c = str.charAt( nStrPos )) != ' '; ++nStrPos )
buf.append( c );
info._date = buf.toString();
while (nStrPos < nStrLen && str.charAt( nStrPos ) == ' ')
++nStrPos;
}
// read full, name, eMail
buf = new StringBuffer();
StringBuffer name = new StringBuffer();
StringBuffer eMail = new StringBuffer();
boolean bReadMail = false;
for ( ; nStrPos < nStrLen; ++nStrPos )
{
c = str.charAt( nStrPos );
switch (c)
{
case '<':
bReadMail = true;
break;
case '>':
bReadMail = false;
break;
default:
if (bReadMail)
eMail.append( c );
else
name.append( c );
}
buf.append( c );
}
info._fullName = buf.toString();
info._eMail = eMail.toString().trim();
info._name = name.toString().trim();
}
if (bPub)
pub.addElement( info );
else
sig.addElement( info );
if (DEBUG)
System.out.println( "### info (" + (bPub ? "pub" : "sig") + ") :" + info );
}
// copy user infos to array
int nPos = 0;
_publicUsers = new UserInfo[pub.size()];
for ( Enumeration enum = pub.elements(); enum.hasMoreElements(); )
_publicUsers[nPos++] = (UserInfo)enum.nextElement();
nPos = 0;
_signatureUsers = new UserInfo[sig.size()];
for ( Enumeration enum = sig.elements(); enum.hasMoreElements(); )
_signatureUsers[nPos++] = (UserInfo)enum.nextElement();
return true;
}
public String toString()
{
return _name;
}
public PGP263x()
{
try
{
// public ring
Runtime runtime = Runtime.getRuntime();
Process proc
;
BufferedReader in;
proc = runtime.exec( new String[] { "pgp", "+batchmode", "+language=en", "-fkvv" } );
BufferedReader err = new BufferedReader( new InputStreamReader( proc.getErrorStream() ) );
_bIsAvailable = false;
String str;
while ((str = err.readLine()) != null)
{
int nIndex = str.indexOf( "2.6.3" );
if (nIndex >= 0)
{
int nEnd = str.indexOf( nIndex, ' ' );
if (nEnd < 0)
nEnd = nIndex + 5;
_name = "PGP " + str.substring( nIndex, nEnd );
if (DEBUG)
System.out.println( "### PGP name=\""+_name+"\"" );
_bIsAvailable = getUserInfos( proc.getInputStream() );
if (_signatureUsers.length > 0) // default user is first in list
_defaultUser = _signatureUsers[0];
break;
}
}
proc.waitFor();
}
catch (InterruptedException exc)
{
if (DEBUG)
{
System.out.println( "### PGP263x InterruptedException: "+exc.getMessage() );
exc.printStackTrace();
}
}
catch (java.io.IOException exc)
{
if (DEBUG)
{
System.out.println( "### PGP263x IOException: "+exc.getMessage() );
exc.printStackTrace();
}
}
if (DEBUG && !_bIsAvailable)
System.out.println( "### PGP263x not available!" );
}
//______________________________________________________________________________________________
public UserInfo[] getPublicUserInfos()
{
return _publicUsers;
}
//______________________________________________________________________________________________
public boolean hasPublicUserInfos()
{
return (_publicUsers != null && _publicUsers.length > 0);
}
//______________________________________________________________________________________________
public UserInfo[] getSignatureUserInfos()
{
return _signatureUsers;
}
//______________________________________________________________________________________________
public boolean hasSignatureUserInfos()
{
return (_signatureUsers != null && _signatureUsers.length > 0);
}
//______________________________________________________________________________________________
public UserInfo getDefaultPrivateUserInfo()
{
return _defaultUser;
}
/**
* checks if pgp 2.6.3 is available
*/
//______________________________________________________________________________________________
public boolean isAvailable()
{
return _bIsAvailable;
}
//______________________________________________________________________________________________
void assumeAvailable() throws PGPException
{
if (! isAvailable())
throw new PGPException( "PGP 2.6.3 is not available!" );
}
//______________________________________________________________________________________________
private SignatureInfo processPGP( String params[], XInputStream input, XOutputStream output )
throws IOException, PGPException
{
assumeAvailable();
if (DEBUG)
{
System.out.print( "# cmd: " );
for ( int i = 0; i < params.length; ++i )
System.out.print( params[i]+" " );
System.out.println();
}
// optional
Runtime runtime = Runtime.getRuntime();
Process proc = null;
SignatureInfo info = null;
try
{
proc = runtime.exec( params );
// provide input
OutputStream stdout = proc.getOutputStream();
final int buffSize= 0x10000;
byte[][] bytes= new byte[1][];
int nRead;
do
{
nRead = input.readBytes( bytes, buffSize );
if (DEBUG)
{
System.out.print( "# writing stdout: " );
byte ar[]= bytes[0];
for ( int n = 0; n < nRead; ++n )
{
System.out.print( (char)ar[n] );
if (n < ar.length-1)
System.out.print( ", " );
}
}
stdout.write( bytes[0], 0, nRead);
}
while (nRead == buffSize);
if (DEBUG)
System.out.println( "# input given. closing stdout" );
stdout.close();
// scan error stream
BufferedReader stderr = new BufferedReader( new InputStreamReader( proc.getErrorStream() ) );
String str;
while ((str = stderr.readLine()) != null)
{
if (str.indexOf( "Error:" ) == 1)
{
proc.waitFor();
throw new PGPException( str.substring( 7 ).trim() );
}
}
// provide output data
InputStream stdin = proc.getInputStream();
byte buf[] = new byte[0x10000];
while ((nRead = stdin.read( buf )) >= 0)
{
if (DEBUG)
{
System.out.print( "# writing output from stdin: " );
for ( int n = 0; n < nRead; ++n )
{
System.out.print( (char)buf[n] );
if (n < buf.length-1)
System.out.print( ", " );
}
}
//output.writeBytes( buf );
// One could assume that the implementer of XOutputStream
// checks for an EOF or a final token that denotes the end
// of the valid data package. Evidently this is not the
// case therefore we can't blindly write the whole array but
// have to write the same number of bytes as received from
// PGP in the InputS tream (Process.getInputStream()).
// If we wrote the whole buffer, then all the data are interpreted
// as pertaining to the mail.
// workaround: create a buffer with the size of the valid data block
// and use it in output.writeBytes rather then "buf"
if(nRead == -1)
output.writeBytes(buf);
else{
byte accurate[]=new byte[nRead];
System.arraycopy( buf, 0, accurate, 0, nRead);
output.writeBytes( accurate);
}
// ------------------------------------------------------------------
}
if (DEBUG)
System.out.println( "# output given." );
if (DEBUG && info != null)
{
if (info._bSignatureChecked)
System.out.println( "# "+(info._bMessageVerified ? "Good" : "Bad")+" signature for user "+info._userId+"!" );
else
System.out.println( "# user "+info._userId+" could not be verified!" );
}
if (DEBUG)
System.out.println( "### waiting for process to end..." );
proc.waitFor();
if (DEBUG)
{
System.out.println( "### process died." );
System.out.flush();
}
}
catch (InterruptedException exc)
{
throw new PGPException( exc.getMessage() );
}
catch (java.io.IOException exc)
{
throw new IOException( exc.getMessage(), null );
}
return info;
}
/**
* encryption: Recipients (Key-Id) must exist in public key ring
* @param Recipients key Ids
* output is always in ascii radix
*/
//______________________________________________________________________________________________
public void encryptAndSign( UserInfo Signer, String Signersphrase, UserInfo Recipients[],
XInputStream PlainText, XOutputStream CipherText )
throws IOException, PGPException
{
String params[] = new String[Recipients.length +8];
int nPos = 0;
params[nPos++] = "pgp";
params[nPos++] = "+batchmode";
params[nPos++] = "+language=en";
params[nPos++] = "-feas";
for ( int i = Recipients.length; i-- > 0; )
params[nPos++] = "0x" + Recipients[i]._key;
params[nPos++] = "-u";
params[nPos++] = "0x" + Signer._key;
params[nPos++] = "-z";
params[nPos++]= Signersphrase;
processPGP( params, PlainText, CipherText );
}
//______________________________________________________________________________________________
public void encrypt( UserInfo Recipients[], XInputStream PlainText, XOutputStream CipherText )
throws IOException, PGPException
{
String params[] = new String[Recipients.length +4];
int nPos = 0;
params[nPos++] = "pgp";
params[nPos++] = "+batchmode";
params[nPos++] = "+language=en";
params[nPos++] = "-fea";
for ( int i = Recipients.length; i-- > 0; )
params[nPos++] = "0x" + Recipients[i]._key;
processPGP( params, PlainText, CipherText );
}
//______________________________________________________________________________________________
/** The pgp option +clearsig=on is default since pgp 2.5.
*/
public void sign( UserInfo Signer, String Signersphrase, boolean bOutputIsAscii,
XInputStream PlainText, XOutputStream SignedText )
throws IOException, PGPException
{
String params[] = bOutputIsAscii
? new String[] { "pgp", "+batchmode", "+language=en", "-fast",
"-u", "0x" + Signer._key, "-z", Signersphrase }
: new String[] { "pgp", "+batchmode", "+language=en", "-fas",
"-u", "0x" + Signer._key, "-z", Signersphrase };
processPGP( params, PlainText, SignedText );
}
/**
* conventional encryption
* output is always in ascii radix
*/
//______________________________________________________________________________________________
public void encryptConv( String Passphrase, XInputStream PlainText, XOutputStream CipherText )
throws IOException, PGPException
{
String params[] = new String[] { "pgp", "+batchmode", "+language=en", "-fac",
"-z", Passphrase };
processPGP( params, PlainText, CipherText );
}
//______________________________________________________________________________________________
public void encryptAndSignConv( UserInfo Signer, String Signersphrase, String Passphrase,
XInputStream PlainText, XOutputStream CipherText )
throws IOException, PGPException
{
String params[] = new String[] { "pgp", "+batchmode", "+language=en", "-fasc",
"-u", "0x" + Signer._key, "-z", Signersphrase,
"-z", Passphrase};
processPGP( params, PlainText, CipherText );
}
/**
* The method doens't explicitly close the input or output stream.
*/
// public SignatureInfo decryptAndVerify( String Passphrase,
// XInputStream CipherText, XOutputStream PlainText )
// throws classic.com.sun.star.io.IOException, PGPException
// {
// String params[] = new String[] { "pgp", "+batchmode", "+language=en", "-f",
// "-z", Passphrase };
// return processPGP( params, CipherText, PlainText );
// }
public SignatureInfo decryptAndVerify( IPassPhraseDialog dialog,
XInputStream aCipherText, XOutputStream aPlainText )
throws IOException, PGPException
{
assumeAvailable();
SignatureInfo info = null;
try
{
byte[][] readBuff= new byte[1][];
int nRead;
int nSizeLine=100;
// read in the first 100 bytes
nRead = aCipherText.readBytes( readBuff, nSizeLine );
// Check if a pass phrase is required. This is not the case if
// the text has just been signed and not encrypted. To find out
// we look for the string "BEGIN PGP SIGNED MESSAGE". This only works
// if pgp was used with options -sat !!!!
// If a pass phrase is required than prompt the user for it
String passPhrase= null;
String text= new String( readBuff[0], 0, nSizeLine); //uses Default byte to character encoding
if( text.indexOf("BEGIN PGP SIGNED MESSAGE") == -1){
passPhrase= dialog.getPassPhrase();
if( passPhrase == null)
return null;
}
//---
String params[] = new String[] { "pgp", "+batchmode", "+language=en", "-f",
"-z", passPhrase };
Runtime runtime = Runtime.getRuntime();
Process proc = null;
proc = runtime.exec( params );
OutputStream stdout = proc.getOutputStream();
// fed pgp with the actual text
stdout.write( readBuff[0], 0, nRead); // write the formerly read data
do
{
nRead = aCipherText.readBytes( readBuff, 0x10000 );
if (DEBUG)
{
System.out.print( "# writing stdout: " );
byte ar[] = readBuff[0];
for ( int n = 0; n < nRead; ++n )
{
System.out.print( (char)ar[n] );
if (n < ar.length-1)
System.out.print( ", " );
}
}
stdout.write( readBuff[0], 0, nRead);
}
while (nRead == 0x10000);
stdout.close();
// scan error stream
BufferedReader stderr = new BufferedReader( new InputStreamReader( proc.getErrorStream() ) );
String str;
while ((str = stderr.readLine()) != null)
{
if (str.indexOf( "Error:" ) == 1)
{
proc.waitFor();
throw new PGPException( str.substring( 7 ).trim() );
}
/* If text was signed then there is an output like:
* Good signature from user "Joachim Lingner <jlingner@gmx.de>".
*/
else if (str.indexOf( "signature from user" ) != -1)
{
String user = str.substring( str.indexOf( '\"' )-1, str.lastIndexOf( '\"' ) ).trim();
info = new SignatureInfo();
info._userId = user;
info._bSignatureChecked = true;
info._bMessageVerified = (str.indexOf( "Good" ) == 0);
}
else if (str.indexOf( "Key matching expected Key ID" ) == 1)
{
String user = str.substring( 28, str.indexOf( "not found" ) ).trim();
info = new SignatureInfo();
info._userId = user;
info._bSignatureChecked = false;
info._bMessageVerified = false;
}
}
// provide output data
InputStream stdin = proc.getInputStream();
byte buf[] = new byte[0x10000];
while ((nRead = stdin.read( buf )) >= 0)
{
if (DEBUG)
{
System.out.print( "# writing output from stdin: " );
for ( int n = 0; n < nRead; ++n )
{
System.out.print( (char)buf[n] );
if (n < buf.length-1)
System.out.print( ", " );
}
}
//output.writeBytes( buf );
// One could assume that the implementer of XOutputStream
// checks for an EOF or a final token that denotes the end
// of the valid data package. Evidently this is not the
// case therefore we can't blindly write the whole array but
// have to write the same number of bytes as received from
// PGP in the InputS tream (Process.getInputStream()).
// If we wrote the whole buffer, then all the data are interpreted
// as pertaining to the mail.
// workaround: create a buffer with the size of the valid data block
// and use it in output.writeBytes rather then "buf"
if(nRead == -1)
aPlainText.writeBytes(buf);
else{
byte accurate[]=new byte[nRead];
System.arraycopy( buf, 0, accurate, 0, nRead);
aPlainText.writeBytes( accurate);
}
// ------------------------------------------------------------------
}
if (DEBUG)
System.out.println( "# output given." );
if (DEBUG && info != null)
{
if (info._bSignatureChecked)
System.out.println( "# "+(info._bMessageVerified ? "Good" : "Bad")+" signature for user "+info._userId+"!" );
else
System.out.println( "# user "+info._userId+" could not be verified!" );
}
if (DEBUG)
System.out.println( "### waiting for process to end..." );
proc.waitFor();
if (DEBUG)
{
System.out.println( "### process died." );
System.out.flush();
}
}
catch (InterruptedException exc)
{
throw new PGPException( exc.getMessage() );
}
catch (java.io.IOException exc)
{
throw new IOException( exc.getMessage(), null );
}
return info;
}
}