diff options
Diffstat (limited to 'core/src/main/j2me/org/spongycastle/crypto')
8 files changed, 1182 insertions, 0 deletions
diff --git a/core/src/main/j2me/org/spongycastle/crypto/encodings/PKCS1Encoding.java b/core/src/main/j2me/org/spongycastle/crypto/encodings/PKCS1Encoding.java new file mode 100644 index 00000000..7af31f36 --- /dev/null +++ b/core/src/main/j2me/org/spongycastle/crypto/encodings/PKCS1Encoding.java @@ -0,0 +1,410 @@ +package org.spongycastle.crypto.encodings; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; + +/** + * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this + * depends on your application - see PKCS1 Version 2 for details. + */ +public class PKCS1Encoding + implements AsymmetricBlockCipher +{ + /** + * some providers fail to include the leading zero in PKCS1 encoded blocks. If you need to + * work with one of these set the system property org.spongycastle.pkcs1.strict to false. + * <p> + * The system property is checked during construction of the encoding object, it is set to + * true by default. + * </p> + */ + public static final String STRICT_LENGTH_ENABLED_PROPERTY = "org.spongycastle.pkcs1.strict"; + + private static final int HEADER_LENGTH = 10; + + private SecureRandom random; + private AsymmetricBlockCipher engine; + private boolean forEncryption; + private boolean forPrivateKey; + private boolean useStrictLength; + private int pLen = -1; + private byte[] fallback = null; + + /** + * Basic constructor. + * @param cipher + */ + public PKCS1Encoding( + AsymmetricBlockCipher cipher) + { + this.engine = cipher; + this.useStrictLength = useStrict(); + } + + /** + * Constructor for decryption with a fixed plaintext length. + * + * @param cipher The cipher to use for cryptographic operation. + * @param pLen Length of the expected plaintext. + */ + public PKCS1Encoding( + AsymmetricBlockCipher cipher, + int pLen) + { + this.engine = cipher; + this.useStrictLength = useStrict(); + this.pLen = pLen; + } + + /** + * Constructor for decryption with a fixed plaintext length and a fallback + * value that is returned, if the padding is incorrect. + * + * @param cipher + * The cipher to use for cryptographic operation. + * @param fallback + * The fallback value, we don't to a arraycopy here. + */ + public PKCS1Encoding( + AsymmetricBlockCipher cipher, + byte[] fallback) + { + this.engine = cipher; + this.useStrictLength = useStrict(); + this.fallback = fallback; + this.pLen = fallback.length; + } + + + + // + // for J2ME compatibility + // + private boolean useStrict() + { + // required if security manager has been installed. + String strict = System.getProperty(STRICT_LENGTH_ENABLED_PROPERTY); + + return strict == null || strict.equals("true"); + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init( + boolean forEncryption, + CipherParameters param) + { + AsymmetricKeyParameter kParam; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + kParam = (AsymmetricKeyParameter)param; + } + + engine.init(forEncryption, param); + + this.forPrivateKey = kParam.isPrivate(); + this.forEncryption = forEncryption; + } + + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return baseBlockSize - HEADER_LENGTH; + } + else + { + return baseBlockSize; + } + } + + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return baseBlockSize - HEADER_LENGTH; + } + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + private byte[] encodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (inLen > getInputBlockSize()) + { + throw new IllegalArgumentException("input data too large"); + } + + byte[] block = new byte[engine.getInputBlockSize()]; + + if (forPrivateKey) + { + block[0] = 0x01; // type code 1 + + for (int i = 1; i != block.length - inLen - 1; i++) + { + block[i] = (byte)0xFF; + } + } + else + { + random.nextBytes(block); // random fill + + block[0] = 0x02; // type code 2 + + // + // a zero byte marks the end of the padding, so all + // the pad bytes must be non-zero. + // + for (int i = 1; i != block.length - inLen - 1; i++) + { + while (block[i] == 0) + { + block[i] = (byte)random.nextInt(); + } + } + } + + block[block.length - inLen - 1] = 0x00; // mark the end of the padding + System.arraycopy(in, inOff, block, block.length - inLen, inLen); + + return engine.processBlock(block, 0, block.length); + } + + /** + * Checks if the argument is a correctly PKCS#1.5 encoded Plaintext + * for encryption. + * + * @param encoded The Plaintext. + * @param pLen Expected length of the plaintext. + * @return Either 0, if the encoding is correct, or -1, if it is incorrect. + */ + private static int checkPkcs1Encoding(byte[] encoded, int pLen) { + int correct = 0; + /* + * Check if the first two bytes are 0 2 + */ + correct |= (encoded[0] ^ 2); + + /* + * Now the padding check, check for no 0 byte in the padding + */ + int plen = encoded.length - ( + pLen /* Lenght of the PMS */ + + 1 /* Final 0-byte before PMS */ + ); + + for (int i = 1; i < plen; i++) { + int tmp = encoded[i]; + tmp |= tmp >> 1; + tmp |= tmp >> 2; + tmp |= tmp >> 4; + correct |= (tmp & 1) - 1; + } + + /* + * Make sure the padding ends with a 0 byte. + */ + correct |= encoded[encoded.length - (pLen +1)]; + + /* + * Return 0 or 1, depending on the result. + */ + correct |= correct >> 1; + correct |= correct >> 2; + correct |= correct >> 4; + return ~((correct & 1) - 1); + } + + + /** + * Decode PKCS#1.5 encoding, and return a random value if the padding is not correct. + * + * @param in The encrypted block. + * @param inOff Offset in the encrypted block. + * @param inLen Length of the encrypted block. + * //@param pLen Length of the desired output. + * @return The plaintext without padding, or a random value if the padding was incorrect. + * + * @throws InvalidCipherTextException + */ + private byte[] decodeBlockOrRandom(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException + { + if (!forPrivateKey) + { + throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing"); + } + + byte[] block = engine.processBlock(in, inOff, inLen); + byte[] random = null; + if (this.fallback == null) + { + random = new byte[this.pLen]; + this.random.nextBytes(random); + } + else + { + random = fallback; + } + + /* + * TODO: This is a potential dangerous side channel. However, you can + * fix this by changing the RSA engine in a way, that it will always + * return blocks of the same length and prepend them with 0 bytes if + * needed. + */ + if (block.length < getOutputBlockSize()) + { + throw new InvalidCipherTextException("block truncated"); + } + + /* + * TODO: Potential side channel. Fix it by making the engine always + * return blocks of the correct length. + */ + if (useStrictLength && block.length != engine.getOutputBlockSize()) + { + throw new InvalidCipherTextException("block incorrect size"); + } + + /* + * Check the padding. + */ + int correct = PKCS1Encoding.checkPkcs1Encoding(block, this.pLen); + + /* + * Now, to a constant time constant memory copy of the decrypted value + * or the random value, depending on the validity of the padding. + */ + byte[] result = new byte[this.pLen]; + for (int i = 0; i < this.pLen; i++) + { + result[i] = (byte)((block[i + (block.length - pLen)] & (~correct)) | (random[i] & correct)); + } + + return result; + } + + /** + * @exception InvalidCipherTextException if the decrypted block is not in PKCS1 format. + */ + private byte[] decodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + /* + * If the length of the expected plaintext is known, we use a constant-time decryption. + * If the decryption fails, we return a random value. + */ + if (this.pLen != -1) { + return this.decodeBlockOrRandom(in, inOff, inLen); + } + + byte[] block = engine.processBlock(in, inOff, inLen); + + if (block.length < getOutputBlockSize()) + { + throw new InvalidCipherTextException("block truncated"); + } + + byte type = block[0]; + + if (forPrivateKey) + { + if (type != 2) + { + throw new InvalidCipherTextException("unknown block type"); + } + } + else + { + if (type != 1) + { + throw new InvalidCipherTextException("unknown block type"); + } + } + + if (useStrictLength && block.length != engine.getOutputBlockSize()) + { + throw new InvalidCipherTextException("block incorrect size"); + } + + // + // find and extract the message block. + // + int start; + + for (start = 1; start != block.length; start++) + { + byte pad = block[start]; + + if (pad == 0) + { + break; + } + if (type == 1 && pad != (byte)0xff) + { + throw new InvalidCipherTextException("block padding incorrect"); + } + } + + start++; // data should start at the next byte + + if (start > block.length || start < HEADER_LENGTH) + { + throw new InvalidCipherTextException("no data in block"); + } + + byte[] result = new byte[block.length - start]; + + System.arraycopy(block, start, result, 0, result.length); + + return result; + } +} diff --git a/core/src/main/j2me/org/spongycastle/crypto/examples/MIDPTest.java b/core/src/main/j2me/org/spongycastle/crypto/examples/MIDPTest.java new file mode 100644 index 00000000..32908a67 --- /dev/null +++ b/core/src/main/j2me/org/spongycastle/crypto/examples/MIDPTest.java @@ -0,0 +1,177 @@ +package org.spongycastle.crypto.examples; + +import java.io.*; +import java.lang.*; + +import javax.microedition.midlet.MIDlet; +import javax.microedition.lcdui.*; + +import org.spongycastle.util.test.*; +import org.spongycastle.util.encoders.*; + +import org.spongycastle.crypto.*; +import org.spongycastle.crypto.paddings.*; +import org.spongycastle.crypto.engines.*; +import org.spongycastle.crypto.modes.*; +import org.spongycastle.crypto.params.*; + +/** + * MIDP is a simple graphics application for the J2ME CLDC/MIDP. + * + * It has hardcoded values for the key and plain text. It also performs the + * standard testing for the chosen cipher, and displays the results. + * + * This example shows how to use the light-weight API and a symmetric cipher. + * + */ +public class MIDPTest extends MIDlet +{ + private Display d = null; + + private boolean doneEncrypt = false; + + private String key = "0123456789abcdef0123456789abcdef"; + private String plainText = "www.bouncycastle.org"; + private byte[] keyBytes = null; + private byte[] cipherText = null; + private BufferedBlockCipher cipher = null; + + private String[] cipherNames = {"DES", "DESede", "IDEA", "Rijndael", "Twofish"}; + + private Form output = null; + + public void startApp() + { + Display.getDisplay(this).setCurrent(output); + } + + public void pauseApp() + { + + } + + public void destroyApp(boolean unconditional) + { + + } + + public MIDPTest() + { + output = new Form("BouncyCastle"); + output.append("Key: " + key.substring(0, 7) + "...\n"); + output.append("In : " + plainText.substring(0, 7) + "...\n"); + + cipherText = performEncrypt(Hex.decode(key.getBytes()), plainText); + String ctS = new String(Hex.encode(cipherText)); + + output.append("\nCT : " + ctS.substring(0, 7) + "...\n"); + + String decryptText = performDecrypt(Hex.decode(key.getBytes()), cipherText); + + output.append("PT : " + decryptText.substring(0, 7) + "...\n"); + + if (decryptText.compareTo(plainText) == 0) + { + output.append("Success"); + } + else + { + output.append("Failure"); + message("[" + plainText + "]"); + message("[" + decryptText + "]"); + } + + } + + private byte[] performEncrypt(byte[] key, String plainText) + { + byte[] ptBytes = plainText.getBytes(); + + cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(getEngineInstance())); + + String name = cipher.getUnderlyingCipher().getAlgorithmName(); + message("Using " + name); + + cipher.init(true, new KeyParameter(key)); + + byte[] rv = new byte[cipher.getOutputSize(ptBytes.length)]; + + int oLen = cipher.processBytes(ptBytes, 0, ptBytes.length, rv, 0); + try + { + cipher.doFinal(rv, oLen); + } + catch (CryptoException ce) + { + message("Ooops, encrypt exception"); + status(ce.toString()); + } + return rv; + } + + private String performDecrypt(byte[] key, byte[] cipherText) + { + cipher.init(false, new KeyParameter(key)); + + byte[] rv = new byte[cipher.getOutputSize(cipherText.length)]; + + int oLen = cipher.processBytes(cipherText, 0, cipherText.length, rv, 0); + try + { + cipher.doFinal(rv, oLen); + } + catch (CryptoException ce) + { + message("Ooops, decrypt exception"); + status(ce.toString()); + } + return new String(rv).trim(); + } + + private int whichCipher() + { + return 4; // DES + } + + private BlockCipher getEngineInstance() + { + // returns a block cipher according to the current + // state of the radio button lists. This is only + // done prior to encryption. + BlockCipher rv = null; + + switch (whichCipher()) + { + case 0 : + rv = new DESEngine(); + break; + case 1 : + rv = new DESedeEngine(); + break; + case 2 : + rv = new IDEAEngine(); + break; + case 3 : + rv = new RijndaelEngine(); + break; + case 4 : + rv = new TwofishEngine(); + break; + default : + rv = new DESEngine(); + break; + } + return rv; + } + + public void message(String s) + { + System.out.println("M:" + s); + } + + public void status(String s) + { + System.out.println("S:" + s); + } + +} diff --git a/core/src/main/j2me/org/spongycastle/crypto/examples/midp_test.jad b/core/src/main/j2me/org/spongycastle/crypto/examples/midp_test.jad new file mode 100644 index 00000000..584f3156 --- /dev/null +++ b/core/src/main/j2me/org/spongycastle/crypto/examples/midp_test.jad @@ -0,0 +1,6 @@ +MIDlet-1: MIDPTest, , org.spongycastle.crypto.examples.MIDPTest +MIDlet-Name: MIDPTest +MIDlet-Jar-Size: 300000 +MIDlet-Jar-URL: midp_test.jar +MIDlet-Vendor: The Legion of the Bouncy Castle +MIDlet-Version: 1.0.0 diff --git a/core/src/main/j2me/org/spongycastle/crypto/examples/midp_test.mf b/core/src/main/j2me/org/spongycastle/crypto/examples/midp_test.mf new file mode 100644 index 00000000..27352109 --- /dev/null +++ b/core/src/main/j2me/org/spongycastle/crypto/examples/midp_test.mf @@ -0,0 +1,7 @@ +MIDlet-1: MIDPTTest, , org.spongycastle.crypto.examples.MIDPTest +MIDlet-Name: MIDPTest +MIDlet-Version: 1.0.0 +MIDlet-Vendor: Jon Eaves +Created-By: 1.3.1 (Sun Microsystems Inc.) +MicroEdition-Configuration: CLDC-1.0 +MicroEdition-Profile: MIDP-1.0 diff --git a/core/src/main/j2me/org/spongycastle/crypto/params/SkeinParameters.java b/core/src/main/j2me/org/spongycastle/crypto/params/SkeinParameters.java new file mode 100644 index 00000000..b29cd441 --- /dev/null +++ b/core/src/main/j2me/org/spongycastle/crypto/params/SkeinParameters.java @@ -0,0 +1,258 @@ +package org.spongycastle.crypto.params; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.digests.SkeinDigest; +import org.spongycastle.crypto.digests.SkeinEngine; +import org.spongycastle.crypto.macs.SkeinMac; +import org.spongycastle.util.Integers; + +/** + * Parameters for the Skein hash function - a series of byte[] strings identified by integer tags. + * <p/> + * Parameterised Skein can be used for: + * <ul> + * <li>MAC generation, by providing a {@link SkeinParameters.Builder#setKey(byte[]) key}.</li> + * <li>Randomised hashing, by providing a {@link SkeinParameters.Builder#setNonce(byte[]) nonce}.</li> + * <li>A hash function for digital signatures, associating a + * {@link SkeinParameters.Builder#setPublicKey(byte[]) public key} with the message digest.</li> + * <li>A key derivation function, by providing a + * {@link SkeinParameters.Builder#setKeyIdentifier(byte[]) key identifier}.</li> + * <li>Personalised hashing, by providing a + * {@link SkeinParameters.Builder#setPersonalisation(Date, String, String) recommended format} or + * {@link SkeinParameters.Builder#setPersonalisation(byte[]) arbitrary} personalisation string.</li> + * </ul> + * + * @see SkeinEngine + * @see SkeinDigest + * @see SkeinMac + */ +public class SkeinParameters + implements CipherParameters +{ + /** + * The parameter type for a secret key, supporting MAC or KDF functions: {@value + * #PARAM_TYPE_KEY}. + */ + public static final int PARAM_TYPE_KEY = 0; + + /** + * The parameter type for the Skein configuration block: {@value #PARAM_TYPE_CONFIG}. + */ + public static final int PARAM_TYPE_CONFIG = 4; + + /** + * The parameter type for a personalisation string: {@value #PARAM_TYPE_PERSONALISATION}. + */ + public static final int PARAM_TYPE_PERSONALISATION = 8; + + /** + * The parameter type for a public key: {@value #PARAM_TYPE_PUBLIC_KEY}. + */ + public static final int PARAM_TYPE_PUBLIC_KEY = 12; + + /** + * The parameter type for a key identifier string: {@value #PARAM_TYPE_KEY_IDENTIFIER}. + */ + public static final int PARAM_TYPE_KEY_IDENTIFIER = 16; + + /** + * The parameter type for a nonce: {@value #PARAM_TYPE_NONCE}. + */ + public static final int PARAM_TYPE_NONCE = 20; + + /** + * The parameter type for the message: {@value #PARAM_TYPE_MESSAGE}. + */ + public static final int PARAM_TYPE_MESSAGE = 48; + + /** + * The parameter type for the output transformation: {@value #PARAM_TYPE_OUTPUT}. + */ + public static final int PARAM_TYPE_OUTPUT = 63; + + private Hashtable parameters; + + public SkeinParameters() + { + this(new Hashtable()); + } + + private SkeinParameters(final Hashtable parameters) + { + this.parameters = parameters; + } + + /** + * Obtains a map of type (Integer) to value (byte[]) for the parameters tracked in this object. + */ + public Hashtable getParameters() + { + return parameters; + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_KEY key parameter}, or <code>null</code> if not + * set. + */ + public byte[] getKey() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_KEY)); + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_PERSONALISATION personalisation parameter}, or + * <code>null</code> if not set. + */ + public byte[] getPersonalisation() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_PERSONALISATION)); + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_PUBLIC_KEY public key parameter}, or + * <code>null</code> if not set. + */ + public byte[] getPublicKey() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_PUBLIC_KEY)); + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_KEY_IDENTIFIER key identifier parameter}, or + * <code>null</code> if not set. + */ + public byte[] getKeyIdentifier() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_KEY_IDENTIFIER)); + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_NONCE nonce parameter}, or <code>null</code> if + * not set. + */ + public byte[] getNonce() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_NONCE)); + } + + /** + * A builder for {@link SkeinParameters}. + */ + public static class Builder + { + private Hashtable parameters = new Hashtable(); + + public Builder() + { + } + + public Builder(Hashtable paramsMap) + { + Enumeration keys = paramsMap.keys(); + while (keys.hasMoreElements()) + { + Integer key = (Integer)keys.nextElement(); + parameters.put(key, paramsMap.get(key)); + } + } + + public Builder(SkeinParameters params) + { + Enumeration keys = params.parameters.keys(); + while (keys.hasMoreElements()) + { + Integer key = (Integer)keys.nextElement(); + parameters.put(key, params.parameters.get(key)); + } + } + + /** + * Sets a parameters to apply to the Skein hash function.<br> + * Parameter types must be in the range 0,5..62, and cannot use the value {@value + * SkeinParameters#PARAM_TYPE_MESSAGE} (reserved for message body). + * <p/> + * Parameters with type < {@value SkeinParameters#PARAM_TYPE_MESSAGE} are processed before + * the message content, parameters with type > {@value SkeinParameters#PARAM_TYPE_MESSAGE} + * are processed after the message and prior to output. + * + * @param type the type of the parameter, in the range 5..62. + * @param value the byte sequence of the parameter. + * @return + */ + public Builder set(int type, byte[] value) + { + if (value == null) + { + throw new IllegalArgumentException("Parameter value must not be null."); + } + if ((type != PARAM_TYPE_KEY) + && (type <= PARAM_TYPE_CONFIG || type >= PARAM_TYPE_OUTPUT || type == PARAM_TYPE_MESSAGE)) + { + throw new IllegalArgumentException("Parameter types must be in the range 0,5..47,49..62."); + } + if (type == PARAM_TYPE_CONFIG) + { + throw new IllegalArgumentException("Parameter type " + PARAM_TYPE_CONFIG + + " is reserved for internal use."); + } + this.parameters.put(Integers.valueOf(type), value); + return this; + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_KEY} parameter. + */ + public Builder setKey(byte[] key) + { + return set(PARAM_TYPE_KEY, key); + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_PERSONALISATION} parameter. + */ + public Builder setPersonalisation(byte[] personalisation) + { + return set(PARAM_TYPE_PERSONALISATION, personalisation); + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_KEY_IDENTIFIER} parameter. + */ + public Builder setPublicKey(byte[] publicKey) + { + return set(PARAM_TYPE_PUBLIC_KEY, publicKey); + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_KEY_IDENTIFIER} parameter. + */ + public Builder setKeyIdentifier(byte[] keyIdentifier) + { + return set(PARAM_TYPE_KEY_IDENTIFIER, keyIdentifier); + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_NONCE} parameter. + */ + public Builder setNonce(byte[] nonce) + { + return set(PARAM_TYPE_NONCE, nonce); + } + + /** + * Constructs a new {@link SkeinParameters} instance with the parameters provided to this + * builder. + */ + public SkeinParameters build() + { + return new SkeinParameters(parameters); + } + } +} diff --git a/core/src/main/j2me/org/spongycastle/crypto/tls/OCSPStatusRequest.java b/core/src/main/j2me/org/spongycastle/crypto/tls/OCSPStatusRequest.java new file mode 100644 index 00000000..ea68af90 --- /dev/null +++ b/core/src/main/j2me/org/spongycastle/crypto/tls/OCSPStatusRequest.java @@ -0,0 +1,131 @@ +package org.spongycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ocsp.ResponderID; +import org.spongycastle.asn1.x509.Extensions; + +/** + * RFC 3546 3.6 + */ +public class OCSPStatusRequest +{ + protected Vector responderIDList; + protected Extensions requestExtensions; + + /** + * @param responderIDList + * a {@link Vector} of {@link ResponderID}, specifying the list of trusted OCSP + * responders. An empty list has the special meaning that the responders are + * implicitly known to the server - e.g., by prior arrangement. + * @param requestExtensions + * OCSP request extensions. A null value means that there are no extensions. + */ + public OCSPStatusRequest(Vector responderIDList, Extensions requestExtensions) + { + this.responderIDList = responderIDList; + this.requestExtensions = requestExtensions; + } + + /** + * @return a {@link Vector} of {@link ResponderID} + */ + public Vector getResponderIDList() + { + return responderIDList; + } + + /** + * @return OCSP request extensions + */ + public Extensions getRequestExtensions() + { + return requestExtensions; + } + + /** + * Encode this {@link OCSPStatusRequest} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + if (responderIDList == null || responderIDList.isEmpty()) + { + TlsUtils.writeUint16(0, output); + } + else + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + for (int i = 0; i < responderIDList.size(); ++i) + { + ResponderID responderID = (ResponderID) responderIDList.elementAt(i); + byte[] derEncoding = responderID.getEncoded(ASN1Encoding.DER); + TlsUtils.writeOpaque16(derEncoding, buf); + } + TlsUtils.checkUint16(buf.size()); + TlsUtils.writeUint16(buf.size(), output); + output.write(buf.toByteArray()); + } + + if (requestExtensions == null) + { + TlsUtils.writeUint16(0, output); + } + else + { + byte[] derEncoding = requestExtensions.getEncoded(ASN1Encoding.DER); + TlsUtils.checkUint16(derEncoding.length); + TlsUtils.writeUint16(derEncoding.length, output); + output.write(derEncoding); + } + } + + /** + * Parse an {@link OCSPStatusRequest} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return an {@link OCSPStatusRequest} object. + * @throws IOException + */ + public static OCSPStatusRequest parse(InputStream input) throws IOException + { + Vector responderIDList = new Vector(); + { + int length = TlsUtils.readUint16(input); + if (length > 0) + { + byte[] data = TlsUtils.readFully(length, input); + ByteArrayInputStream buf = new ByteArrayInputStream(data); + do + { + byte[] derEncoding = TlsUtils.readOpaque16(buf); + ResponderID responderID = ResponderID.getInstance(TlsUtils.readDERObject(derEncoding)); + responderIDList.addElement(responderID); + } + while (buf.available() > 0); + } + } + + Extensions requestExtensions = null; + { + int length = TlsUtils.readUint16(input); + if (length > 0) + { + byte[] derEncoding = TlsUtils.readFully(length, input); + requestExtensions = Extensions.getInstance(TlsUtils.readDERObject(derEncoding)); + } + } + + return new OCSPStatusRequest(responderIDList, requestExtensions); + } +} diff --git a/core/src/main/j2me/org/spongycastle/crypto/tls/ServerNameList.java b/core/src/main/j2me/org/spongycastle/crypto/tls/ServerNameList.java new file mode 100644 index 00000000..09817e6b --- /dev/null +++ b/core/src/main/j2me/org/spongycastle/crypto/tls/ServerNameList.java @@ -0,0 +1,86 @@ +package org.spongycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +public class ServerNameList +{ + protected Vector serverNameList; + + /** + * @param serverNameList a {@link Vector} of {@link ServerName}. + */ + public ServerNameList(Vector serverNameList) + { + if (serverNameList == null || serverNameList.isEmpty()) + { + throw new IllegalArgumentException("'serverNameList' must not be null or empty"); + } + + this.serverNameList = serverNameList; + } + + /** + * @return a {@link Vector} of {@link ServerName}. + */ + public Vector getServerNameList() + { + return serverNameList; + } + + /** + * Encode this {@link ServerNameList} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + for (int i = 0; i < serverNameList.size(); ++i) + { + ServerName entry = (ServerName)serverNameList.elementAt(i); + entry.encode(buf); + } + + TlsUtils.checkUint16(buf.size()); + TlsUtils.writeUint16(buf.size(), output); + output.write(buf.toByteArray()); + } + + /** + * Parse a {@link ServerNameList} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link ServerNameList} object. + * @throws IOException + */ + public static ServerNameList parse(InputStream input) throws IOException + { + int length = TlsUtils.readUint16(input); + if (length < 1) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + byte[] data = TlsUtils.readFully(length, input); + + ByteArrayInputStream buf = new ByteArrayInputStream(data); + + Vector server_name_list = new Vector(); + while (buf.available() > 0) + { + ServerName entry = ServerName.parse(buf); + server_name_list.addElement(entry); + } + + return new ServerNameList(server_name_list); + } +} diff --git a/core/src/main/j2me/org/spongycastle/crypto/tls/UDPTransport.java b/core/src/main/j2me/org/spongycastle/crypto/tls/UDPTransport.java new file mode 100644 index 00000000..ea287144 --- /dev/null +++ b/core/src/main/j2me/org/spongycastle/crypto/tls/UDPTransport.java @@ -0,0 +1,107 @@ +package org.spongycastle.crypto.tls; + +import java.io.IOException; +import javax.microedition.io.DatagramConnection; +import javax.microedition.io.Datagram; + +public class UDPTransport + implements DatagramTransport +{ + + protected final static int MIN_IP_OVERHEAD = 20; + protected final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64; + protected final static int UDP_OVERHEAD = 8; + + protected final DatagramConnection socket; + protected final int receiveLimit, sendLimit; + + public UDPTransport(DatagramConnection socket, int mtu) + throws IOException + { + // + // In 1.3 and earlier sockets were bound and connected during creation + // + //if (!socket.isBound() || !socket.isConnected()) + //{ + // throw new IllegalArgumentException("'socket' must be bound and connected"); + //} + + this.socket = socket; + + // NOTE: As of JDK 1.6, can use NetworkInterface.getMTU + + this.receiveLimit = mtu - MIN_IP_OVERHEAD - UDP_OVERHEAD; + this.sendLimit = mtu - MAX_IP_OVERHEAD - UDP_OVERHEAD; + } + + public int getReceiveLimit() + { + return receiveLimit; + } + + public int getSendLimit() + { + // TODO[DTLS] Implement Path-MTU discovery? + return sendLimit; + } + + public int receive(byte[] buf, int off, int len, int waitMillis) + throws IOException + { + //socket.setSoTimeout(waitMillis); -- not applicable + + if (off == 0) + { + Datagram packet = socket.newDatagram(buf, len); + socket.receive(packet); + + return packet.getLength(); + } + else + { + byte[] rv = new byte[len]; + + Datagram packet = socket.newDatagram(rv, len); + socket.receive(packet); + + System.arraycopy(rv, 0, buf, off, packet.getLength()); + + return packet.getLength(); + } + } + + public void send(byte[] buf, int off, int len) + throws IOException + { + if (len > getSendLimit()) + { + /* + * RFC 4347 4.1.1. "If the application attempts to send a record larger than the MTU, + * the DTLS implementation SHOULD generate an error, thus avoiding sending a packet + * which will be fragmented." + */ + // TODO Exception + } + + if (off == 0) + { + Datagram packet = socket.newDatagram(buf, len); + socket.send(packet); + } + else + { + byte[] data = new byte[len]; + + System.arraycopy(buf, off, data, 0, len); + + Datagram packet = socket.newDatagram(data, len); + socket.send(packet); + } + } + + public void close() + throws IOException + { + socket.close(); + } +} |