blob: e308423b4279127a5f133accffb73d2845fec0ba [file] [log] [blame]
/// Converts an Object Identifier from one format to another.
///
/// Shows the BER encoding for a dotted OID or decodes a BER encoding and
/// shows its value as a dotted OID.
///
/// Example:
/// oid-util 2.16.840.1.101.3.4.2.1
/// oid-util 0609608648016503040201
///
/// This program was written to check some of the "magic values" in the
/// Pointy Castle source code.
import 'dart:typed_data';
//----------------------------------------------------------------
/// Represent bytes in hexadecimal
///
/// If a [separator] is provided, it is placed the hexadecimal characters
/// representing each byte. Otherwise, all the hexadecimal characters are
/// simply concatenated together.
String bin2hex(Uint8List bytes, {String? separator, int? wrap}) {
var len = 0;
final buf = StringBuffer();
for (final b in bytes) {
final s = b.toRadixString(16);
if (buf.isNotEmpty && separator != null) {
buf.write(separator);
len += separator.length;
}
if (wrap != null && wrap < len + 2) {
buf.write('\n');
len = 0;
}
buf.write('${(s.length == 1) ? '0' : ''}$s');
len += 2;
}
return buf.toString();
}
//----------------------------------------------------------------
// Decode a hexadecimal string into a sequence of bytes.
Uint8List hex2bin(String hexStr) {
if (hexStr.length % 2 != 0) {
throw const FormatException('not an even number of hexadecimal characters');
}
final result = Uint8List(hexStr.length ~/ 2);
for (var i = 0; i < result.length; i++) {
result[i] = int.parse(hexStr.substring(2 * i, 2 * (i + 1)), radix: 16);
}
return result;
}
//----------------------------------------------------------------
List<int> decodeBERObjectIdentifier(Uint8List bytes) {
if (bytes.length < 2) {
throw const FormatException('BER missing tag and length');
}
// final tag = bytes[0];
// Decode the length
int contentLength;
int contentStart;
var n = bytes[1] & 0x7F;
if (bytes[1] & 0x80 == 0) {
// Length is <128 and encoded completely in the second byte
contentStart = 2; // skipping over tag and one-byte length
contentLength = n;
} else {
// Length is encoded in the following n bytes
if (bytes.length < 1 + 1 + n) {
throw const FormatException('BER length is incomplete');
}
contentStart = 1 + n; // skipping over tag and bytes for the length
contentLength = 0;
while (0 < n) {
contentLength = (contentLength << 8) | bytes[n];
n--;
}
}
if (contentLength == 0) {
throw const FormatException('BER has no content');
}
if (bytes.length < contentStart + contentLength) {
throw const FormatException('BER is incomplete');
}
// Decode the content as an Object Identifier
// First two components are encoded in the first byte
final components = <int>[bytes[contentStart] ~/ 40, bytes[contentStart] % 40];
if (components[0] == 0 || components[1] == 0) {
throw const FormatException('invalid zero OID component');
}
// Process rest of bytes in the content
var v = 0; // partial or complete component value
for (var n = contentStart + 1; n < contentStart + contentLength; n++) {
final byte = bytes[n];
v = (v << 7) + (byte & 0x7F);
if (byte & 0x80 == 0) {
// Value is complete
if (v == 0) {
throw const FormatException('invalid zero OID component');
}
components.add(v);
v = 0;
} else {
// Value continues in next byte(s)
assert(v != 0);
}
}
if (v != 0) {
throw const FormatException('incomplete OID content');
}
if ((contentStart + contentLength) < bytes.length) {
throw const FormatException(('extra bytes after OID'));
}
return components;
}
//----------------------------------------------------------------
Uint8List encodeBERObjectIdentifier(String oidStr, {int tag = 0x06}) {
assert((0 <= tag) && (tag <= 255), 'tag does not fit into a byte');
// Convert the string "1.2.3.4" into list of numbers [1, 2, 3, 4]
final components = oidStr.split('.').map(int.parse);
// Encode the content
final bytes = <int>[tag, -1]; // with a placeholder for a one-byte length
late int firstComponent;
var componentIndex = 0;
for (final component in components) {
if (component <= 0) {
throw FormatException('invalid OID component: $component');
}
if (componentIndex == 0) {
// Save for processing with the second component
firstComponent = component;
} else if (componentIndex == 1) {
// Encode first two components into the first byte
bytes.add(firstComponent * 40 + component);
} else {
// Subsequent components
if (component <= 0x7F) {
// Represent with a single byte
bytes.add(component);
} else {
// Represent by multiple bytes
final code = <int>[];
var v = component;
while (0 < v) {
code.add((v & 0x7F) | 0x80); // 7-bits and the continue flag set
v >>= 7;
}
code[0] &= 0x7F; // clear flag on least-significant-byte
bytes.addAll(code.reversed); // include with most-significant-byte first
}
}
componentIndex++;
}
// Fix up tag and length
if (bytes.length - 2 < 127) {
// Length can be represented by a single byte: use the bytes as the result
bytes[1] = (bytes.length - 2);
return Uint8List.fromList(bytes);
} else {
// Length needs multiple bytes: create bigger list and copy the bytes to it
final lengthEnc = <int>[]; // first encode the length (temporary LSB order)
var v = (bytes.length - 2);
while (0 < v) {
lengthEnc.add(v & 0x7F);
v >>= 7;
}
// Create a new list and populate it
final result = Uint8List(1 + 1 + lengthEnc.length + (bytes.length - 2));
result[0] = tag;
result[1] = lengthEnc.length | 0x80; // number bytes representing length
result.insertAll(2, lengthEnc.reversed); // in correct MSB order
final startOfContent = 1 + 1 + lengthEnc.length;
for (var x = 0; x < bytes.length - 2; x++) {
result[startOfContent + x] = bytes[2 + x];
}
return result;
}
}
//----------------------------------------------------------------
int main(List<String> args) {
if (args.isNotEmpty && !args.contains('-h') && !args.contains('--help')) {
for (final str in args) {
try {
if (str.contains('.')) {
// Encode a OID into BER
final enc = encodeBERObjectIdentifier(str);
print(bin2hex(enc));
} else {
// Decode hex BER into components and show in dotted form
final bytes = hex2bin(str);
final components = decodeBERObjectIdentifier(bytes);
print(components.map((n) => '$n').join('.'));
}
} catch (e) {
print('Error: $str: $e');
}
}
} else {
print('Usage: oid-util values...');
print(' values are either dotted OIDs (e.g. "2.16.840.1.101.3.4.2.1") or');
print(' DER encoding of OIDs in hex (e.g. "0609608648016503040201")');
print(' This program convert from one format to the other.');
}
return 0;
}