net.freeutils.util.DERParser Maven / Gradle / Ivy
Show all versions of jelementary Show documentation
/*
* Copyright © 2003-2024 Amichai Rothman
*
* This file is part of JElementary - the Java Elementary Utilities package.
*
* JElementary is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* JElementary 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JElementary. If not, see .
*
* For additional info see https://www.freeutils.net/source/jelementary/
*/
package net.freeutils.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* An X.690 ASN.1 DER data parser.
*
* The current implementation is very basic and supports only a small part of the spec.
*/
public class DERParser {
final InputStream in;
int tag;
long len;
/**
* Constructs a DERParser for parsing the DER data in the given stream.
*
* @param in the DER data
*/
public DERParser(InputStream in) {
this.in = in;
}
/**
* Constructs a DERParser for parsing the DER data in the given byte array.
*
* @param data the DER data
*/
public DERParser(byte[] data) {
this(new ByteArrayInputStream(data));
}
/**
* Reads a single byte of data from the stream as an integer.
*
* @return the read byte of data
* @throws IOException if an error occurs or the end of stream has been reached
*/
private int read() throws IOException {
int b = in.read();
if (b < 0)
throw new IOException("unexpected end of stream");
return b;
}
/**
* Reads a tag field from the stream.
*
* @return the read tag
* @throws IOException if an error occurs
* @throws UnsupportedOperationException if attempting to read a long form tag
*/
private int readTag() throws IOException {
int tag = read();
if ((tag & 0x1f) == 0x1f)
throw new UnsupportedOperationException("long tags are not supported yet");
return tag;
}
/**
* Reads a length field from the stream.
*
* @return the read tag
* @throws IOException if an error occurs
* @throws UnsupportedOperationException if attempting to read a length value more than 7 bytes long
*/
private long readLength() throws IOException {
long len = read();
if ((len & 0x80) != 0) {
int octets = (int)len & 0x7f;
if (octets > 7)
throw new UnsupportedOperationException("length greater than 7 bytes is not supported");
len = 0;
for (int i = 0; i < octets; i++)
len = len << 8 | read();
}
return len;
}
/**
* Reads the next value's tag and length from the stream, making it the new current value.
*
* The field data must be read using {@link #getContent}, {@link #parseContent()}
* or {@link #skipContent()} before this method is called again.
*
* @throws IOException if an error occurs
*/
public void next() throws IOException {
tag = readTag();
len = readLength();
}
/**
* Reads the next value's tag and length fields from the stream, making it the new current value,
* while ensuring that the tag matches the given expected tag.
*
* The field data must be read using {@link #getContent}, {@link #parseContent()}
* or {@link #skipContent()} before this method is called again.
*
* @param expectedTag the expected tag
* @throws IOException if an error occurs or the read tag does not match the expected tag
*/
public void next(int expectedTag) throws IOException {
next();
if (tag != expectedTag)
throw new IOException("unexpected tag " + tag);
}
/**
* Returns the tag of the current value.
*
* @return the tag of the current value
*/
public int getTag() {
return tag;
}
/**
* Returns the length of the current value.
*
* @return the length of the current value
*/
public long getLength() {
return len;
}
/**
* Skips the content of the current value.
*
* @throws IOException if an error occurs
*/
public void skipContent() throws IOException {
getContent(); // TODO: optimize to skip without reading
}
/**
* Reads the raw content of the current value.
*
* @return the raw content of the current value
* @throws IOException if an error occurs
*/
public byte[] getContent() throws IOException {
byte[] data = new byte[(int)len];
int read = in.read(data); // TODO: read fully
if (read < len)
throw new IOException("unexpected end of stream (missing data)");
return data;
}
/**
* Reads the content of the current value
* and returns a {@code DERParser} to parse it.
*
* @return a parser for the content of the current value
* @throws IOException if an error occurs
*/
public DERParser parseContent() throws IOException {
return new DERParser(getContent()); // TODO: use substream instead of array
}
}