diff options
| -rw-r--r-- | openvpn/src/openvpn/options.c | 4 | ||||
| -rw-r--r-- | res/values/strings.xml | 1 | ||||
| -rw-r--r-- | src/de/blinkt/openvpn/ConfigConverter.java | 1 | ||||
| -rw-r--r-- | src/de/blinkt/openvpn/FileSelect.java | 2 | ||||
| -rw-r--r-- | src/de/blinkt/openvpn/OpenVPN.java | 6 | ||||
| -rw-r--r-- | src/de/blinkt/openvpn/OpenVpnManagementThread.java | 40 | ||||
| -rw-r--r-- | src/de/blinkt/openvpn/VpnProfile.java | 100 | ||||
| -rw-r--r-- | src/org/spongycastle/util/encoders/Base64.java | 121 | ||||
| -rw-r--r-- | src/org/spongycastle/util/encoders/Base64Encoder.java | 298 | ||||
| -rw-r--r-- | src/org/spongycastle/util/encoders/Encoder.java | 17 | ||||
| -rw-r--r-- | src/org/spongycastle/util/io/pem/PemGenerationException.java | 26 | ||||
| -rw-r--r-- | src/org/spongycastle/util/io/pem/PemHeader.java | 66 | ||||
| -rw-r--r-- | src/org/spongycastle/util/io/pem/PemObject.java | 62 | ||||
| -rw-r--r-- | src/org/spongycastle/util/io/pem/PemObjectGenerator.java | 7 | ||||
| -rw-r--r-- | src/org/spongycastle/util/io/pem/PemWriter.java | 138 | 
15 files changed, 839 insertions, 50 deletions
| diff --git a/openvpn/src/openvpn/options.c b/openvpn/src/openvpn/options.c index 7b7fc684..b83c1de6 100644 --- a/openvpn/src/openvpn/options.c +++ b/openvpn/src/openvpn/options.c @@ -2730,6 +2730,10 @@ options_postprocess_filechecks (struct options *options)    errs |= check_file_access (CHKACC_FILE|CHKACC_INLINE, options->cert_file, R_OK, "--cert");    errs |= check_file_access (CHKACC_FILE|CHKACC_INLINE, options->extra_certs_file, R_OK,                               "--extra-certs"); + +#ifdef MANAGMENT_EXTERNAL_KEY +	if(!(options->management_flags | MF_EXTERNAL_KEY)) +#endif    errs |= check_file_access (CHKACC_FILE|CHKACC_INLINE, options->priv_key_file, R_OK,                               "--key");    errs |= check_file_access (CHKACC_FILE|CHKACC_INLINE, options->pkcs12_file, R_OK, diff --git a/res/values/strings.xml b/res/values/strings.xml index 64b8e28b..ba4f6b02 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -220,5 +220,6 @@      <string name="keppstatus_summary">Keep the notification displayed after the connection is established to show traffic statistics.</string>      <string name="keepstatus">Show Traffic Statistics</string>      <string name="mobile_info">Running on %1$s (%2$s) %3$s, Android API %4$d</string> +    <string name="error_rsa_sign">Error signing with Android keystore key %s</string>  </resources> diff --git a/src/de/blinkt/openvpn/ConfigConverter.java b/src/de/blinkt/openvpn/ConfigConverter.java index 5e0a6eb3..3ccda053 100644 --- a/src/de/blinkt/openvpn/ConfigConverter.java +++ b/src/de/blinkt/openvpn/ConfigConverter.java @@ -134,6 +134,7 @@ public class ConfigConverter extends ListActivity {  						filedata += new String(buf,0,len);
  						len = fis.read(buf);
  					}
 +					fis.close();
  					return filedata;
  				} catch (FileNotFoundException e) {
  					log(e.getLocalizedMessage());
 diff --git a/src/de/blinkt/openvpn/FileSelect.java b/src/de/blinkt/openvpn/FileSelect.java index c235594e..bbad5cfd 100644 --- a/src/de/blinkt/openvpn/FileSelect.java +++ b/src/de/blinkt/openvpn/FileSelect.java @@ -113,7 +113,7 @@ public class FileSelect extends Activity {  				data += new String(buf,0,len);  				len=fis.read(buf);  			} -			 +			fis.close();  			mData =data;  			mInlineFragment.setData(data);  			getActionBar().selectTab(inlineFileTab); diff --git a/src/de/blinkt/openvpn/OpenVPN.java b/src/de/blinkt/openvpn/OpenVPN.java index 64ecf17c..db980200 100644 --- a/src/de/blinkt/openvpn/OpenVPN.java +++ b/src/de/blinkt/openvpn/OpenVPN.java @@ -147,7 +147,7 @@ public class OpenVPN {  	} -	public static void updateStateString(String state, String msg) { +	public synchronized static void updateStateString(String state, String msg) {  		for (StateListener sl : stateListener) {  			sl.updateState(state,msg);  		} @@ -179,7 +179,9 @@ public class OpenVPN {  	public static void logError(int ressourceId) {  		newlogItem(new LogItem(LogItem.ERROR, ressourceId));  	} - +	public static void logError(int ressourceId, Object... args) { +		newlogItem(new LogItem(LogItem.ERROR, ressourceId,args)); +	}  } diff --git a/src/de/blinkt/openvpn/OpenVpnManagementThread.java b/src/de/blinkt/openvpn/OpenVpnManagementThread.java index f23d9d9b..e1b37342 100644 --- a/src/de/blinkt/openvpn/OpenVpnManagementThread.java +++ b/src/de/blinkt/openvpn/OpenVpnManagementThread.java @@ -5,11 +5,20 @@ import java.io.IOException;  import java.io.InputStream;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
 +import java.security.InvalidKeyException;
 +import java.security.NoSuchAlgorithmException;
 +import java.security.PrivateKey;
  import java.util.LinkedList;
  import java.util.Vector;
 +import javax.crypto.BadPaddingException;
 +import javax.crypto.Cipher;
 +import javax.crypto.IllegalBlockSizeException;
 +import javax.crypto.NoSuchPaddingException;
 +
  import android.net.LocalSocket;
  import android.os.ParcelFileDescriptor;
 +import android.util.Base64;
  import android.util.Log;
  public class OpenVpnManagementThread implements Runnable {
 @@ -173,6 +182,8 @@ public class OpenVpnManagementThread implements Runnable {  				// 1 log level N,I,E etc.
  				// 2 log message
  				OpenVPN.logMessage(0, "",  args[2]);
 +			} else if (cmd.equals("RSA_SIGN")) {
 +				processSignCommand(argument);
  			} else {
  				OpenVPN.logMessage(0, "MGMT:", "Got unrecognized command" + command);
  				Log.i(TAG, "Got unrecognized command" + command);
 @@ -389,4 +400,33 @@ public class OpenVpnManagementThread implements Runnable {  	}
 +	private void processSignCommand(String b64data) {
 +		PrivateKey privkey = mProfile.getKeystoreKey();
 +		Exception err =null;
 +		try{
 +			byte[] data = Base64.decode(b64data, Base64.DEFAULT);
 +			Cipher rsasinger = javax.crypto.Cipher.getInstance("RSA/ECB/PKCS1PADDING");
 +			rsasinger.init(Cipher.ENCRYPT_MODE, privkey);
 +
 +			byte[] signed_bytes = rsasinger.doFinal(data);
 +			String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
 +			managmentCommand("rsa-sig\n");
 +			managmentCommand(signed_string);
 +			managmentCommand("\nEND\n");
 +		} catch (NoSuchAlgorithmException e){
 +			err =e;
 +		} catch (InvalidKeyException e) {
 +			err =e;
 +		} catch (NoSuchPaddingException e) {
 +			err =e;
 +		} catch (IllegalBlockSizeException e) {
 +			err =e;
 +		} catch (BadPaddingException e) {
 +			err =e;
 +		}
 +		if(err !=null) {
 +			OpenVPN.logError(R.string.error_rsa_sign,err.getLocalizedMessage());
 +		}
 +	}
 +
  }
 diff --git a/src/de/blinkt/openvpn/VpnProfile.java b/src/de/blinkt/openvpn/VpnProfile.java index dd729a06..8b758b3b 100644 --- a/src/de/blinkt/openvpn/VpnProfile.java +++ b/src/de/blinkt/openvpn/VpnProfile.java @@ -4,25 +4,23 @@ import java.io.ByteArrayInputStream;  import java.io.File;  import java.io.FileInputStream;  import java.io.FileNotFoundException; -import java.io.FileOutputStream;  import java.io.FileReader;  import java.io.FileWriter;  import java.io.IOException;  import java.io.InputStream;  import java.io.Serializable; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException;  import java.security.PrivateKey;  import java.security.cert.Certificate;  import java.security.cert.CertificateException;  import java.security.cert.CertificateFactory;  import java.security.cert.X509Certificate;  import java.util.Collection; -import java.util.Random;  import java.util.UUID;  import java.util.Vector; +import org.spongycastle.util.io.pem.PemObject; +import org.spongycastle.util.io.pem.PemWriter; +  import android.content.Context;  import android.content.Intent;  import android.content.pm.ApplicationInfo; @@ -51,8 +49,7 @@ public class VpnProfile implements  Serializable{  	protected transient String mTransientPW=null;  	protected transient String mTransientPCKS12PW=null; - -	private static transient String mTempPKCS12Password; +	private transient PrivateKey mPrivateKey;  	public static String DEFAULT_DNS1="131.234.137.23";  	public static String DEFAULT_DNS2="131.234.137.24"; @@ -100,6 +97,7 @@ public class VpnProfile implements  Serializable{  	public boolean mUseDefaultRoutev6=true;  	public String mCustomRoutesv6="";  	public String mKeyPassword=""; +	  	public void clearDefaults() { @@ -122,7 +120,8 @@ public class VpnProfile implements  Serializable{  	} -	static final String OVPNCONFIGPKCS12 = "android.pkcs12"; +	static final String OVPNCONFIGCA = "android-ca.pem"; +	static final String OVPNCONFIGUSERCERT = "android-user.pem";  	public VpnProfile(String name) { @@ -223,9 +222,10 @@ public class VpnProfile implements  Serializable{  		case VpnProfile.TYPE_USERPASS_KEYSTORE:  			cfg+="auth-user-pass\n";  		case VpnProfile.TYPE_KEYSTORE: -			cfg+="pkcs12 "; -			cfg+=cacheDir.getAbsolutePath() + "/" + OVPNCONFIGPKCS12; -			cfg+="\n"; +			cfg+="ca " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGCA + "\n"; +			cfg+="cert " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGUSERCERT + "\n"; +			cfg+="management-external-key\n"; +			  			break;  		case VpnProfile.TYPE_USERPASS:  			cfg+="auth-user-pass\n"; @@ -447,7 +447,7 @@ public class VpnProfile implements  Serializable{  		Intent intent = new Intent(context,OpenVpnService.class);  		if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { -			savePKCS12(context); +			saveCertificates(context);  		}  		intent.putExtra(prefix + ".ARGV" , buildOpenvpnArgv(context.getCacheDir())); @@ -468,27 +468,13 @@ public class VpnProfile implements  Serializable{  		return intent;  	} -	private String getTemporaryPKCS12Password() { -		if(mTempPKCS12Password!=null) -			return mTempPKCS12Password; - -		String pw= ""; -		// Put enough digits togher to make a password :) -		Random r = new Random(); -		for(int i=0;i < 4;i++) { -			pw +=  Integer.valueOf(r.nextInt(1000)).toString(); -		} - -		mTempPKCS12Password=pw; -		return mTempPKCS12Password; - -	} - -	private void savePKCS12(Context context) { +	private void saveCertificates(Context context) {  		PrivateKey privateKey = null;  		X509Certificate[] cachain=null;  		try {  			privateKey = KeyChain.getPrivateKey(context,mAlias); +			mPrivateKey = privateKey; +			  			cachain = KeyChain.getCertificateChain(context, mAlias);  			if(cachain.length <= 1 && !nonNull(mCaFilename))  				OpenVPN.logMessage(0, "", context.getString(R.string.keychain_nocacert)); @@ -496,32 +482,50 @@ public class VpnProfile implements  Serializable{  			for(X509Certificate cert:cachain) {  				OpenVPN.logInfo(R.string.cert_from_keystore,cert.getSubjectDN());  			} +		 +			 -			KeyStore ks = KeyStore.getInstance("PKCS12"); -			ks.load(null, null); +  			if(nonNull(mCaFilename)) {  				try { -				Certificate cacert = getCacertFromFile(); -				 -				ks.setCertificateEntry("cacert", cacert); +					Certificate cacert = getCacertFromFile(); +					X509Certificate[] newcachain = new X509Certificate[cachain.length+1]; +					for(int i=0;i<cachain.length;i++) +						newcachain[i]=cachain[i]; +					 +					newcachain[cachain.length-1]=(X509Certificate) cacert; +					  				} catch (Exception e) {  					OpenVPN.logError("Could not read CA certificate" + e.getLocalizedMessage());  				}  			} -			ks.setKeyEntry("usercert", privateKey, null, cachain); -			String mypw = getTemporaryPKCS12Password(); -			FileOutputStream fout = new FileOutputStream(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGPKCS12); -			ks.store(fout,mypw.toCharArray()); -			fout.flush(); fout.close(); + +			 +			FileWriter fout = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGCA); +			PemWriter pw = new PemWriter(fout); +			for(X509Certificate cert:cachain) { +				pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded())); +			} + +			pw.close(); +			 +			 +			if(cachain.length>= 1){ +				X509Certificate usercert = cachain[0]; + +				FileWriter userout = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGUSERCERT); + +				PemWriter upw = new PemWriter(userout); +				upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded())); +				upw.close(); + +			} +			  			return;  		} catch (InterruptedException e) {  			e.printStackTrace(); -		} catch (KeyStoreException e) { -			e.printStackTrace();  		} catch (FileNotFoundException e) {  			e.printStackTrace(); -		} catch (NoSuchAlgorithmException e) { -			e.printStackTrace();  		} catch (CertificateException e) {  			e.printStackTrace();  		} catch (IOException e) { @@ -574,10 +578,6 @@ public class VpnProfile implements  Serializable{  			return pwcopy;  		}  		switch (mAuthenticationType) { -		case TYPE_KEYSTORE: -		case TYPE_USERPASS_KEYSTORE: -			return getTemporaryPKCS12Password(); -  		case TYPE_PKCS12:  		case TYPE_USERPASS_PKCS12:  			return mPKCS12Password; @@ -623,6 +623,7 @@ public class VpnProfile implements  Serializable{  					data += new String(buf,0,len);  					len = fr.read(buf);  				} +				fr.close();  			} catch (FileNotFoundException e) {  				return false;  			} catch (IOException e) { @@ -684,6 +685,11 @@ public class VpnProfile implements  Serializable{  	} +	public PrivateKey getKeystoreKey() { +		return mPrivateKey; +	} + +  } diff --git a/src/org/spongycastle/util/encoders/Base64.java b/src/org/spongycastle/util/encoders/Base64.java new file mode 100644 index 00000000..87bd80a0 --- /dev/null +++ b/src/org/spongycastle/util/encoders/Base64.java @@ -0,0 +1,121 @@ +package org.spongycastle.util.encoders; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class Base64 +{ +    private static final Encoder encoder = new Base64Encoder(); +     +    /** +     * encode the input data producing a base 64 encoded byte array. +     * +     * @return a byte array containing the base 64 encoded data. +     */ +    public static byte[] encode( +        byte[]    data) +    { +        int len = (data.length + 2) / 3 * 4; +        ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); +         +        try +        { +            encoder.encode(data, 0, data.length, bOut); +        } +        catch (IOException e) +        { +            throw new RuntimeException("exception encoding base64 string: " + e); +        } +         +        return bOut.toByteArray(); +    } + +    /** +     * Encode the byte data to base 64 writing it to the given output stream. +     * +     * @return the number of bytes produced. +     */ +    public static int encode( +        byte[]                data, +        OutputStream    out) +        throws IOException +    { +        return encoder.encode(data, 0, data.length, out); +    } +     +    /** +     * Encode the byte data to base 64 writing it to the given output stream. +     * +     * @return the number of bytes produced. +     */ +    public static int encode( +        byte[]                data, +        int                    off, +        int                    length, +        OutputStream    out) +        throws IOException +    { +        return encoder.encode(data, off, length, out); +    } +     +    /** +     * decode the base 64 encoded input data. It is assumed the input data is valid. +     * +     * @return a byte array representing the decoded data. +     */ +    public static byte[] decode( +        byte[]    data) +    { +        int len = data.length / 4 * 3; +        ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); +         +        try +        { +            encoder.decode(data, 0, data.length, bOut); +        } +        catch (IOException e) +        { +            throw new RuntimeException("exception decoding base64 string: " + e); +        } +         +        return bOut.toByteArray(); +    } +     +    /** +     * decode the base 64 encoded String data - whitespace will be ignored. +     * +     * @return a byte array representing the decoded data. +     */ +    public static byte[] decode( +        String    data) +    { +        int len = data.length() / 4 * 3; +        ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); +         +        try +        { +            encoder.decode(data, bOut); +        } +        catch (IOException e) +        { +            throw new RuntimeException("exception decoding base64 string: " + e); +        } +         +        return bOut.toByteArray(); +    } +     +    /** +     * decode the base 64 encoded String data writing it to the given output stream, +     * whitespace characters will be ignored. +     * +     * @return the number of bytes produced. +     */ +    public static int decode( +        String                data, +        OutputStream    out) +        throws IOException +    { +        return encoder.decode(data, out); +    } +} diff --git a/src/org/spongycastle/util/encoders/Base64Encoder.java b/src/org/spongycastle/util/encoders/Base64Encoder.java new file mode 100644 index 00000000..84060707 --- /dev/null +++ b/src/org/spongycastle/util/encoders/Base64Encoder.java @@ -0,0 +1,298 @@ +package org.spongycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +public class Base64Encoder +    implements Encoder +{ +    protected final byte[] encodingTable = +        { +            (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', +            (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', +            (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', +            (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', +            (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', +            (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', +            (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', +            (byte)'v', +            (byte)'w', (byte)'x', (byte)'y', (byte)'z', +            (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', +            (byte)'7', (byte)'8', (byte)'9', +            (byte)'+', (byte)'/' +        }; + +    protected byte    padding = (byte)'='; +     +    /* +     * set up the decoding table. +     */ +    protected final byte[] decodingTable = new byte[128]; + +    protected void initialiseDecodingTable() +    { +        for (int i = 0; i < encodingTable.length; i++) +        { +            decodingTable[encodingTable[i]] = (byte)i; +        } +    } +     +    public Base64Encoder() +    { +        initialiseDecodingTable(); +    } +     +    /** +     * encode the input data producing a base 64 output stream. +     * +     * @return the number of bytes produced. +     */ +    public int encode( +        byte[]                data, +        int                    off, +        int                    length, +        OutputStream    out)  +        throws IOException +    { +        int modulus = length % 3; +        int dataLength = (length - modulus); +        int a1, a2, a3; +         +        for (int i = off; i < off + dataLength; i += 3) +        { +            a1 = data[i] & 0xff; +            a2 = data[i + 1] & 0xff; +            a3 = data[i + 2] & 0xff; + +            out.write(encodingTable[(a1 >>> 2) & 0x3f]); +            out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); +            out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); +            out.write(encodingTable[a3 & 0x3f]); +        } + +        /* +         * process the tail end. +         */ +        int    b1, b2, b3; +        int    d1, d2; + +        switch (modulus) +        { +        case 0:        /* nothing left to do */ +            break; +        case 1: +            d1 = data[off + dataLength] & 0xff; +            b1 = (d1 >>> 2) & 0x3f; +            b2 = (d1 << 4) & 0x3f; + +            out.write(encodingTable[b1]); +            out.write(encodingTable[b2]); +            out.write(padding); +            out.write(padding); +            break; +        case 2: +            d1 = data[off + dataLength] & 0xff; +            d2 = data[off + dataLength + 1] & 0xff; + +            b1 = (d1 >>> 2) & 0x3f; +            b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; +            b3 = (d2 << 2) & 0x3f; + +            out.write(encodingTable[b1]); +            out.write(encodingTable[b2]); +            out.write(encodingTable[b3]); +            out.write(padding); +            break; +        } + +        return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); +    } + +    private boolean ignore( +        char    c) +    { +        return (c == '\n' || c =='\r' || c == '\t' || c == ' '); +    } +     +    /** +     * decode the base 64 encoded byte data writing it to the given output stream, +     * whitespace characters will be ignored. +     * +     * @return the number of bytes produced. +     */ +    public int decode( +        byte[]          data, +        int             off, +        int             length, +        OutputStream    out) +        throws IOException +    { +        byte    b1, b2, b3, b4; +        int     outLen = 0; +         +        int     end = off + length; +         +        while (end > off) +        { +            if (!ignore((char)data[end - 1])) +            { +                break; +            } +             +            end--; +        } +         +        int  i = off; +        int  finish = end - 4; +         +        i = nextI(data, i, finish); + +        while (i < finish) +        { +            b1 = decodingTable[data[i++]]; +             +            i = nextI(data, i, finish); +             +            b2 = decodingTable[data[i++]]; +             +            i = nextI(data, i, finish); +             +            b3 = decodingTable[data[i++]]; +             +            i = nextI(data, i, finish); +             +            b4 = decodingTable[data[i++]]; + +            out.write((b1 << 2) | (b2 >> 4)); +            out.write((b2 << 4) | (b3 >> 2)); +            out.write((b3 << 6) | b4); +             +            outLen += 3; +             +            i = nextI(data, i, finish); +        } + +        outLen += decodeLastBlock(out, (char)data[end - 4], (char)data[end - 3], (char)data[end - 2], (char)data[end - 1]); +         +        return outLen; +    } + +    private int nextI(byte[] data, int i, int finish) +    { +        while ((i < finish) && ignore((char)data[i])) +        { +            i++; +        } +        return i; +    } +     +    /** +     * decode the base 64 encoded String data writing it to the given output stream, +     * whitespace characters will be ignored. +     * +     * @return the number of bytes produced. +     */ +    public int decode( +        String          data, +        OutputStream    out) +        throws IOException +    { +        byte    b1, b2, b3, b4; +        int     length = 0; +         +        int     end = data.length(); +         +        while (end > 0) +        { +            if (!ignore(data.charAt(end - 1))) +            { +                break; +            } +             +            end--; +        } +         +        int  i = 0; +        int  finish = end - 4; +         +        i = nextI(data, i, finish); +         +        while (i < finish) +        { +            b1 = decodingTable[data.charAt(i++)]; +             +            i = nextI(data, i, finish); +             +            b2 = decodingTable[data.charAt(i++)]; +             +            i = nextI(data, i, finish); +             +            b3 = decodingTable[data.charAt(i++)]; +             +            i = nextI(data, i, finish); +             +            b4 = decodingTable[data.charAt(i++)]; + +            out.write((b1 << 2) | (b2 >> 4)); +            out.write((b2 << 4) | (b3 >> 2)); +            out.write((b3 << 6) | b4); +             +            length += 3; +             +            i = nextI(data, i, finish); +        } + +        length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1)); + +        return length; +    } + +    private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4)  +        throws IOException +    { +        byte    b1, b2, b3, b4; +         +        if (c3 == padding) +        { +            b1 = decodingTable[c1]; +            b2 = decodingTable[c2]; + +            out.write((b1 << 2) | (b2 >> 4)); +             +            return 1; +        } +        else if (c4 == padding) +        { +            b1 = decodingTable[c1]; +            b2 = decodingTable[c2]; +            b3 = decodingTable[c3]; + +            out.write((b1 << 2) | (b2 >> 4)); +            out.write((b2 << 4) | (b3 >> 2)); +             +            return 2; +        } +        else +        { +            b1 = decodingTable[c1]; +            b2 = decodingTable[c2]; +            b3 = decodingTable[c3]; +            b4 = decodingTable[c4]; + +            out.write((b1 << 2) | (b2 >> 4)); +            out.write((b2 << 4) | (b3 >> 2)); +            out.write((b3 << 6) | b4); +             +            return 3; +        }  +    } + +    private int nextI(String data, int i, int finish) +    { +        while ((i < finish) && ignore(data.charAt(i))) +        { +            i++; +        } +        return i; +    } +} diff --git a/src/org/spongycastle/util/encoders/Encoder.java b/src/org/spongycastle/util/encoders/Encoder.java new file mode 100644 index 00000000..106c36b7 --- /dev/null +++ b/src/org/spongycastle/util/encoders/Encoder.java @@ -0,0 +1,17 @@ +package org.spongycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Encode and decode byte arrays (typically from binary to 7-bit ASCII  + * encodings). + */ +public interface Encoder +{ +    int encode(byte[] data, int off, int length, OutputStream out) throws IOException; +     +    int decode(byte[] data, int off, int length, OutputStream out) throws IOException; + +    int decode(String data, OutputStream out) throws IOException; +} diff --git a/src/org/spongycastle/util/io/pem/PemGenerationException.java b/src/org/spongycastle/util/io/pem/PemGenerationException.java new file mode 100644 index 00000000..0127ca0c --- /dev/null +++ b/src/org/spongycastle/util/io/pem/PemGenerationException.java @@ -0,0 +1,26 @@ +package org.spongycastle.util.io.pem; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class PemGenerationException +    extends IOException +{ +    private Throwable cause; + +    public PemGenerationException(String message, Throwable cause) +    { +        super(message); +        this.cause = cause; +    } + +    public PemGenerationException(String message) +    { +        super(message); +    } + +    public Throwable getCause() +    { +        return cause; +    } +} diff --git a/src/org/spongycastle/util/io/pem/PemHeader.java b/src/org/spongycastle/util/io/pem/PemHeader.java new file mode 100644 index 00000000..4adb815e --- /dev/null +++ b/src/org/spongycastle/util/io/pem/PemHeader.java @@ -0,0 +1,66 @@ +package org.spongycastle.util.io.pem; + +public class PemHeader +{ +    private String name; +    private String value; + +    public PemHeader(String name, String value) +    { +        this.name = name; +        this.value = value; +    } + +    public String getName() +    { +        return name; +    } + +    public String getValue() +    { +        return value; +    } + +    public int hashCode() +    { +        return getHashCode(this.name) + 31 * getHashCode(this.value);     +    } + +    public boolean equals(Object o) +    { +        if (!(o instanceof PemHeader)) +        { +            return false; +        } + +        PemHeader other = (PemHeader)o; + +        return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value)); +    } + +    private int getHashCode(String s) +    { +        if (s == null) +        { +            return 1; +        } + +        return s.hashCode(); +    } + +    private boolean isEqual(String s1, String s2) +    { +        if (s1 == s2) +        { +            return true; +        } + +        if (s1 == null || s2 == null) +        { +            return false; +        } + +        return s1.equals(s2); +    } + +} diff --git a/src/org/spongycastle/util/io/pem/PemObject.java b/src/org/spongycastle/util/io/pem/PemObject.java new file mode 100644 index 00000000..6f7c79c5 --- /dev/null +++ b/src/org/spongycastle/util/io/pem/PemObject.java @@ -0,0 +1,62 @@ +package org.spongycastle.util.io.pem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("all") +public class PemObject +    implements PemObjectGenerator +{ +	private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); + +    private String type; +    private List   headers; +    private byte[] content; + +    /** +     * Generic constructor for object without headers. +     * +     * @param type pem object type. +     * @param content the binary content of the object. +     */ +    public PemObject(String type, byte[] content) +    { +        this(type, EMPTY_LIST, content); +    } + +    /** +     * Generic constructor for object with headers. +     * +     * @param type pem object type. +     * @param headers a list of PemHeader objects. +     * @param content the binary content of the object. +     */ +    public PemObject(String type, List headers, byte[] content) +    { +        this.type = type; +        this.headers = Collections.unmodifiableList(headers); +        this.content = content; +    } + +    public String getType() +    { +        return type; +    } + +    public List getHeaders() +    { +        return headers; +    } + +    public byte[] getContent() +    { +        return content; +    } + +    public PemObject generate() +        throws PemGenerationException +    { +        return this; +    } +} diff --git a/src/org/spongycastle/util/io/pem/PemObjectGenerator.java b/src/org/spongycastle/util/io/pem/PemObjectGenerator.java new file mode 100644 index 00000000..1a8cea6d --- /dev/null +++ b/src/org/spongycastle/util/io/pem/PemObjectGenerator.java @@ -0,0 +1,7 @@ +package org.spongycastle.util.io.pem; + +public interface PemObjectGenerator +{ +    PemObject generate() +        throws PemGenerationException; +} diff --git a/src/org/spongycastle/util/io/pem/PemWriter.java b/src/org/spongycastle/util/io/pem/PemWriter.java new file mode 100644 index 00000000..f5a6a363 --- /dev/null +++ b/src/org/spongycastle/util/io/pem/PemWriter.java @@ -0,0 +1,138 @@ +package org.spongycastle.util.io.pem; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; + +import org.spongycastle.util.encoders.Base64; + +/** + * A generic PEM writer, based on RFC 1421 + */ +@SuppressWarnings("all") +public class PemWriter +    extends BufferedWriter +{ +    private static final int LINE_LENGTH = 64; + +    private final int nlLength; +    private char[]  buf = new char[LINE_LENGTH]; + +    /** +     * Base constructor. +     * +     * @param out output stream to use. +     */ +    public PemWriter(Writer out) +    { +        super(out); + +        String nl = System.getProperty("line.separator"); +        if (nl != null) +        { +            nlLength = nl.length(); +        } +        else +        { +            nlLength = 2; +        } +    } + +    /** +     * Return the number of bytes or characters required to contain the +     * passed in object if it is PEM encoded. +     * +     * @param obj pem object to be output +     * @return an estimate of the number of bytes +     */ +    public int getOutputSize(PemObject obj) +    { +        // BEGIN and END boundaries. +        int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4; + +        if (!obj.getHeaders().isEmpty()) +        { +            for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) +            { +                PemHeader hdr = (PemHeader)it.next(); + +                size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength; +            } + +            size += nlLength; +        } + +        // base64 encoding +        int dataLen = ((obj.getContent().length + 2) / 3) * 4; +         +        size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength); + +        return size; +    } +     +    public void writeObject(PemObjectGenerator objGen) +        throws IOException +    { +        PemObject obj = objGen.generate(); + +        writePreEncapsulationBoundary(obj.getType()); + +        if (!obj.getHeaders().isEmpty()) +        { +            for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) +            { +                PemHeader hdr = (PemHeader)it.next(); + +                this.write(hdr.getName()); +                this.write(": "); +                this.write(hdr.getValue()); +                this.newLine(); +            } + +            this.newLine(); +        } +         +        writeEncoded(obj.getContent()); +        writePostEncapsulationBoundary(obj.getType()); +    } + +    private void writeEncoded(byte[] bytes) +        throws IOException +    { +        bytes = Base64.encode(bytes); + +        for (int i = 0; i < bytes.length; i += buf.length) +        { +            int index = 0; + +            while (index != buf.length) +            { +                if ((i + index) >= bytes.length) +                { +                    break; +                } +                buf[index] = (char)bytes[i + index]; +                index++; +            } +            this.write(buf, 0, index); +            this.newLine(); +        } +    } + +    private void writePreEncapsulationBoundary( +        String type) +        throws IOException +    { +        this.write("-----BEGIN " + type + "-----"); +        this.newLine(); +    } + +    private void writePostEncapsulationBoundary( +        String type) +        throws IOException +    { +        this.write("-----END " + type + "-----"); +        this.newLine(); +    } +} | 
