www.pudn.com > cryptix-asn1-0.1.11.zip > DerEncoder.java


/* $Id: DerEncoder.java,v 1.4 2001/06/02 02:46:40 raif Exp $ 
 * 
 * Copyright (C) 1997-2001 The Cryptix Foundation Limited. All rights reserved. 
 * 
 * Use, modification, copying and distribution of this software is subject to 
 * the terms and conditions of the Cryptix General Licence. You should have 
 * received a copy of the Cryptix General Licence along with this library; if 
 * not, you can download a copy from http://www.cryptix.org/ 
 */ 
package cryptix.asn1.encoding; 
 
import cryptix.asn1.io.*; 
import cryptix.asn1.lang.*; 
 
import org.apache.log4j.Category; 
 
import java.io.BufferedOutputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.OutputStream; 
import java.io.IOException; 
import java.io.PrintWriter; 
import java.math.BigInteger; 
import java.text.SimpleDateFormat; 
import java.util.ArrayList; 
import java.util.Date; 
import java.util.Iterator; 
import java.util.Properties; 
import java.util.StringTokenizer; 
import java.util.TimeZone; 
 
/** 
 * A class to encode ASN.1 specifications according to the Distinguished 
 * Encoding Rules.

* * @version $Revision: 1.4 $ * @author Raif S. Naffah, Axelle Apvrille */ public class DerEncoder extends ASNWriter implements Cloneable { // Constants and vars // ....................................................................... static Category cat = Category.getInstance(DerEncoder.class.getName()); /** The underlying output stream. */ BufferedOutputStream out; // Constructor(s) // ....................................................................... /** Trivial constructor for use by Factory. */ public DerEncoder() { super(); } // Class method(s) // ....................................................................... // Cloneable interface implementation // ....................................................................... public Object clone() { return new DerEncoder(); } // ASNWriter abstract methods implementation // ....................................................................... /** * Initialises this instance to encode to the designated output stream. * * @param os the designated output stream. * @exception IllegalStateException if this instance is already initialised * with an output stream. Caller should close the previous stream before * invoking this method again. */ public void open(OutputStream os) { if (out != null) throw new IllegalStateException(); this.out = os instanceof BufferedOutputStream ? (BufferedOutputStream) os : new BufferedOutputStream(os, 10240); } public void encodeAny(IType obj, Object val) throws IOException { cat.debug("==> encodeAny()"); if (val instanceof Boolean) this.encodeBoolean(obj, (Boolean) val); else if (val instanceof BigInteger) this.encodeInteger(obj, (BigInteger) val); else if (val instanceof String) this.encodeString(Tag.UNIVERSAL_STRING, obj, (String) val); else if (val instanceof Date) this.encodeUTCTime(obj, (Date) val); else if (val instanceof IType) ((IType) val).encode(this); else this.encodeOctetString(obj, (byte[]) val); // synchronized (out) { // write(obj); // flush(); // } cat.debug("<== encodeAny()"); } public void encodeObjectIdentifier(IType obj, String val) throws IOException { cat.debug("==> encodeObjectIdentifier()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); cat.info(" val=\""+val+"\""); // convert oid components substrings into ints StringTokenizer st = new StringTokenizer(val, "."); int[] component = new int[st.countTokens()]; int i; for (i = 0; i < component.length; i++) component[i] = Integer.parseInt(st.nextToken()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); encodeSubIdentifier(baos, component[0] * 40 + component[1]); // first 2 are special for (i = 2; i < component.length; i++) encodeSubIdentifier(baos, component[i]); byte[] ba = baos.toByteArray(); if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(Tag.OBJECT_IDENTIFIER, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeObjectIdentifier()"); } public void encodeNull(IType obj) throws IOException { cat.debug("==> encodeNull()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); byte[] ba = new byte[0]; if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(Tag.NULL, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeNull()"); } public void encodeBoolean(IType obj, Boolean val) throws IOException { cat.debug("==> encodeBoolean()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); cat.info(" val="+String.valueOf(val)); byte[] ba = new byte[] { (byte)(val.booleanValue() ? 0x01 : 0x00) }; if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(Tag.BOOLEAN, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeBoolean()"); } public void encodeInteger(IType obj, BigInteger val) throws IOException { cat.debug("==> encodeInteger()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); cat.info(" val="+String.valueOf(val)); byte[] ba = val.toByteArray(); if ((ba[0] & 0x80) != 0) throw new IllegalArgumentException("Illegal internal encoding"); if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(Tag.INTEGER, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeInteger()"); } public void encodeString(int tagValue, IType obj, String val) throws IOException { cat.debug("==> encodeString("+tagValue+")"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); cat.info(" val="+String.valueOf(val)); if (tag == null) tag = new Tag(tagValue, false); // implicit tag byte[] ba = val.getBytes("UTF8"); if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(tagValue, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeString()"); } public void encodeBitString(IType obj, byte[] val) throws IOException { cat.debug("==> encodeBitString()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); byte[] ba = new byte[val.length+1]; System.arraycopy(val, 0, ba, 1, val.length); if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(Tag.BIT_STRING, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeBitString()"); } public void encodeOctetString(IType obj, byte[] val) throws IOException { cat.debug("==> encodeOctetString()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); if (tag.isExplicit() && !tag.isUniversal()) val = toTLV(Tag.OCTET_STRING, val); synchronized (out) { encode(tag); encode(val.length); // write the length write(val); // and finally the object's data flush(); } cat.debug("<== encodeOctetString()"); } public void encodeUTCTime(IType obj, Date val) throws IOException { cat.debug("==> encodeUTCTime()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); cat.info(" val="+String.valueOf(val)); SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss'Z'"); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); byte[] ba = sdf.format(val).getBytes(); if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(Tag.UTC_TIME, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeUTCTime()"); } public void encodeGeneralizedTime(IType obj, Date val) throws IOException { cat.debug("==> encodeGeneralizedTime()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); cat.info(" val="+String.valueOf(val)); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss'.'SSS'Z'"); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); byte[] ba = sdf.format(val).getBytes(); if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(Tag.GENERALIZED_TIME, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeGeneralizedTime()"); } public void encodeStructure(IIterativeType obj) throws IOException { cat.debug("==> encodeStructure()"); Tag tag = obj.tag(); cat.info(" tag=\""+tag+"\""); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ASNWriter local = (ASNWriter) this.clone(); local.open(baos); Object element; IType t; ArrayList al; for (Iterator it = obj.iterator(); it.hasNext(); ) { element = it.next(); if (element instanceof IType) { t = (Type) element; cat.info("Encoding (simple) "+t.getClass().getName()+"..."); t.encode(local); } else // SEQUENCE/SET OF for (Iterator ali = ((ArrayList) element).iterator(); ali.hasNext(); ) { t = (Type) ali.next(); cat.info("Encoding (compound) "+t.getClass().getName()+"..."); t.encode(local); } } local.close(); byte[] ba = baos.toByteArray(); if (tag.isExplicit() && !tag.isUniversal()) ba = toTLV(Tag.SEQUENCE, ba); synchronized (out) { encode(tag); encode(ba.length); // write the length write(ba); // and finally the object's data flush(); } cat.debug("<== encodeStructure()"); } // OutputStream methods implementation // ....................................................................... /** * Writes the specified byte to this output stream. The general contract for * write() is that one byte is written to the output stream. The * byte to be written is the eight low-order bits of the argument b. * The 24 high-order bits of are ignored. * * @param b the byte. * @exception IOException if an I/O error occurs. In particular, an * IOException may be thrown if the output stream has been closed. */ public void write(int b) throws IOException { out.write(b & 0xFF); } /** * Closes the underlying output stream and releases any system resources * associated with it. * * @exception IOException if an I/O error occurs. */ public void close() throws IOException { if (out != null) { try { out.close(); } catch (IOException ignored) { cat.warn("I/O exception while closing the stream: "+ignored.getMessage()); } out = null; } } /** * Flushes this output stream and forces any buffered output bytes to be * written out. The general contract of flush() is that calling it is * an indication that, if any bytes previously written have been buffered by * the implementation of the output stream, such bytes should immediately be * written to their intended destination. * * @exception IOException if an I/O error occurs. */ public void flush() throws IOException { out.flush(); } // Tag-related methods // ....................................................................... /** * Outputs the current value of this Tag instance to the * designated output stream. * * @param tag the Tag instance. * @exception IOException if an exception occurs while executing this method. */ private void encode(Tag tag) throws IOException { int t = tag.getClazz(); if (tag.isConstructed()) t |= 0x20; int value = tag.getValue(); if (value < 0x1F) write(t | value); else { write(t | 0x1F); t = value; while (t > 63) { write(63); t -= 63; } write(t); } } // Length-related methods // ....................................................................... /** * Encodes the length element of a DER triplet. * * @param length the size in bytes of a DER value. * @exception IOException if an exception occurs while executing this method. */ private void encode(int length) throws IOException { if (length < 128) { // short definite form write(length); return; } if (length < 256) { // long definite form write(-127); write(length); return; } if (length < 65536) { write(-126); write(length >> 8); write(length); return; } if (length < 16777216) { write(-125); write(length >> 16); write(length >> 8); write(length); return; } write(-124); write(length >> 24); write(length >> 16); write(length >> 8); write(length); } // Other instance methods // ....................................................................... /** * Convenience method.

* * Returns the byte array containing the proper encoding of the designated * byte array as if tagged by the designated UNIVERSAL class value. * * @param universalValue a UNIVERSAL class value to use for tagging. * @param ba the raw data of an object to encode. * @return the proper TLV (DER triplet) encoding. * @exception IOException if an exception occurs while executing this method. */ private byte[] toTLV(int universalValue, byte[] ba) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DerEncoder local = new DerEncoder(); local.open(baos); local.encode(new Tag(universalValue)); local.encode(ba.length); local.write(ba); local.close(); return (baos.toByteArray()); } /** * Encodes OID subidentifier(s) on 7 bits + 1 bit indicating if last byte or * not.

* * BEWARE: This assumes definite length encoding; ie. it will not work * if value is too big and needs more than 4 bytes to be encoded. * * @param baos output stream to write encoding to. * @param c if this represents the first 2 subidentifiers, enter value as * first * 40 + second, otherwise just call with subidentifier's value. * @throws IOException in case we have problems outputting to stream. */ private void encodeSubIdentifier(OutputStream os, int c) throws IOException { cat.debug("==> encodeSubIdentifier(os, "+String.valueOf(c)+")"); int i = 0; byte[] ab = new byte[4]; for ( ; i < 4; i++){ ab[i] = (byte)(c & 0x7F); c >>>= 7; if (c == 0) break; } // all bytes, except last one have 8th bit set to 1 for ( ; i > 0; i--) os.write((ab[i] | 0x80) & 0xFF); os.write(ab[0] & 0xFF); cat.debug("<== encodeSubIdentifier()"); } }