package com.intrbiz.bergamot.crypto.util; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.UUID; import org.bouncycastle.util.encoders.Hex; /** * A semantic certificate Serial number, this combines a UUID with a short revision. * * This allows for certificate Serial numbers to have a meaning, which is useful in * some situation and also allows for a certificate to be revised, without loosing * the unique id. * */ public final class SerialNum { public static final byte VERSION_1 = 0x00; public static final byte VERSION_2 = 0x42; public static final short MODE_NONE = 0x0000; public static final short MODE_AGENT = 0x0001; public static final short MODE_TEMPLATE = 0x0002; private static final byte V2_FORMAT_PREFIX = (byte) 0x5A; private final UUID id; private final short rev; private final byte version; private final short mode; public SerialNum(UUID id, short rev, byte version, short mode) { this.id = id; this.rev = rev; this.version = version; this.mode = mode; } public SerialNum(UUID id, short rev) { this(id, rev, VERSION_1, (short) 0); } public SerialNum(UUID id, int rev) { this(id, (short) rev, VERSION_1, (short) 0); } public SerialNum(UUID id, int rev, int version, int mode) { this(id, (short) rev, (byte) (version & 0xFF), (short) mode); } public UUID getId() { return this.id; } public short getRev() { return this.rev; } public byte getVersion() { return this.version; } public boolean isVersion1() { return this.version == VERSION_1; } public boolean isVersion2() { return this.version == VERSION_2; } public short getMode() { return this.mode; } public boolean isAgent() { return (this.mode & MODE_AGENT) == MODE_AGENT; } public boolean isTemplate() { return (this.mode & MODE_TEMPLATE) == MODE_TEMPLATE; } /** * Return a SerialNum which represents the next revision of this SerialNum, IE: rev + 1 */ public SerialNum revision() { return new SerialNum(this.id, this.rev + 1, this.version, this.mode); } public byte[] toBytes() { if (this.version == VERSION_1) { // old format ByteBuffer bb = ByteBuffer.wrap(new byte[18]); bb.putLong(this.id.getMostSignificantBits()); bb.putLong(this.id.getLeastSignificantBits()); bb.putShort(this.rev); return bb.array(); } else { // version 2+ format // right most 18 bytes are the old version 1 format // the left most byte is a static marker (0x5A) // this stops the array being truncated when wrapped // as a bignum. Versioned data is filled between the // left most marker byte and the version byte // this makes it easy to differentiate between version // 1 and version 2+ structures ByteBuffer bb = ByteBuffer.wrap(new byte[22]); bb.put(V2_FORMAT_PREFIX); // marker bb.putShort(this.mode); bb.put(this.version); bb.putLong(this.id.getMostSignificantBits()); bb.putLong(this.id.getLeastSignificantBits()); bb.putShort(this.rev); return bb.array(); } } public BigInteger toBigInt() { return new BigInteger(1, this.toBytes()); } public String toString() { return new String(Hex.encode(this.toBytes())).toUpperCase(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + mode; result = prime * result + rev; result = prime * result + version; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SerialNum other = (SerialNum) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (mode != other.mode) return false; if (rev != other.rev) return false; if (version != other.version) return false; return true; } public static SerialNum fromBytes(byte[] array) { // validate the minimum length if (array.length < 18) throw new IllegalArgumentException("A serial num is at least 18 bytes long!"); if (array.length < 20) { // old style format ByteBuffer bb = ByteBuffer.wrap(array); bb.position(bb.capacity() - 18); UUID id = new UUID(bb.getLong(), bb.getLong()); short rev = bb.getShort(); return new SerialNum(id, rev); } else { // version 2+ format ByteBuffer bb = ByteBuffer.wrap(array); // read out the uuid and rev (right aligned) bb.position(bb.capacity() - 18); UUID id = new UUID(bb.getLong(), bb.getLong()); short rev = bb.getShort(); // now look left wards and pull out the version bb.position(bb.capacity() - 19); byte version = bb.get(); // parse the data based on version if (version == VERSION_2) { bb.position(bb.capacity() - 21); short mode = bb.getShort(); return new SerialNum(id, rev, version, mode); } else if (version == VERSION_1) { return new SerialNum(id, rev); } else { throw new IllegalArgumentException("Failed to parse serial num binary format, maybe newer version"); } } } public static SerialNum fromString(String serialNumHex) { return fromBytes(Hex.decode(serialNumHex)); } public static SerialNum fromBigInt(BigInteger bigInt) { return fromBytes(bigInt.toByteArray()); } public static SerialNum randomSerialNum() { return randomSerialNum((short) 0); } public static SerialNum fromName(String name) { return fromName(name, (short) 0); } public static SerialNum randomSerialNum(short mode) { return new SerialNum(UUID.randomUUID(), 1, VERSION_2, mode); } public static SerialNum fromName(String name, short mode) { // build UUID from the name return new SerialNum(UUID.nameUUIDFromBytes(name.getBytes(Charset.forName("UTF8"))), 1, VERSION_2, mode); } public static SerialNum version2(UUID id, short rev, short mode) { return new SerialNum(id, rev, VERSION_2, mode); } public static SerialNum version2(UUID id, int rev, int mode) { return new SerialNum(id, (short) rev, VERSION_2, (short) mode); } public static SerialNum version2(UUID id, short rev) { return new SerialNum(id, rev, VERSION_2, (short) 0); } public static SerialNum version2(UUID id, int rev) { return new SerialNum(id, (short) rev, VERSION_2, (short) 0); } public static SerialNum version1(UUID id, short rev) { return new SerialNum(id, rev, VERSION_1, (short) 0); } public static SerialNum version1(UUID id, int rev) { return new SerialNum(id, (short) rev, VERSION_1, (short) 0); } }