Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,447 @@
|
||||
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
|
||||
// Use of this source code is governed by a MIT License
|
||||
// that can be found in the LICENSE file.
|
||||
module asn1
|
||||
|
||||
import math.big
|
||||
import crypto.internal.subtle
|
||||
|
||||
// default_integer_tag is the default of ASN.1 INTEGER type.
|
||||
pub const default_integer_tag = Tag{.universal, false, int(TagType.integer)}
|
||||
|
||||
// ASN.1 INTEGER.
|
||||
//
|
||||
// The INTEGER type value can be a positive or negative number.
|
||||
// There are no limits imposed on the magnitude of INTEGER values in the ASN.1 standard.
|
||||
// Its handles number arbitrary length of number with support of `math.big` module.
|
||||
// But, for sake of safety, we limit the INTEGER limit to follow allowed length in
|
||||
// definite form of Length part, ie, 1008 bit, or 126 bytes
|
||||
// The encoding of an integer number shall be primitive.
|
||||
|
||||
// Limit of length of INTEGER type, in bytes
|
||||
// Known big RSA keys is 4096 bits, ie, 512 bytes
|
||||
const max_integer_length = 2048
|
||||
|
||||
// Integer represent Universal class of arbitrary length type of ASN.1 INTEGER.
|
||||
// The encoding of an integer value shall be primitive.
|
||||
// If the contents octets of an integer value encoding consist of more than one octet,
|
||||
// then the bits of the first octet and bit 8 of the second octet.
|
||||
// a) shall not all be ones; and.
|
||||
// b) shall not all be zero.
|
||||
// NOTE – These rules ensure that an integer value is always encoded in
|
||||
// the smallest possible number of octets.
|
||||
pub struct Integer {
|
||||
pub:
|
||||
// underlying integer value with support from `i64` and `big.Integer`
|
||||
value IntValue
|
||||
}
|
||||
|
||||
// hex returns Integer value as a hex string.
|
||||
pub fn (v Integer) hex() string {
|
||||
match v.value {
|
||||
i64 {
|
||||
val := v.value as i64
|
||||
return val.hex_full()
|
||||
}
|
||||
big.Integer {
|
||||
val := v.value as big.Integer
|
||||
return val.hex()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (v Integer) str() string {
|
||||
return 'Integer ${v.value.str()}'
|
||||
}
|
||||
|
||||
// IntValue represents arbitrary integer value, currently we support
|
||||
// through primitive 164 type for integer value below < max_i64, and
|
||||
// use `big.Integer` for support arbitrary length of integer values.
|
||||
type IntValue = big.Integer | i64
|
||||
|
||||
fn (v IntValue) str() string {
|
||||
match v {
|
||||
i64 {
|
||||
val := v as i64
|
||||
return val.str()
|
||||
}
|
||||
big.Integer {
|
||||
val := v as big.Integer
|
||||
return val.str()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bytes get the bytes representation from underlying IntValue.
|
||||
fn (v IntValue) bytes() []u8 {
|
||||
match v {
|
||||
i64 {
|
||||
return i64_to_bytes(v)
|
||||
}
|
||||
big.Integer {
|
||||
// if v == big.zero_int or similar big.Integer values that produces empty bytes,
|
||||
// returns v.bytes() directly can lead to undesired behavior thats doesn't aligned with
|
||||
// ASN.1 INTEGER requirement. See the discussion on the discord about the issues
|
||||
// at https://discord.com/channels/592103645835821068/592294828432424960/1230460279733620777
|
||||
// so, we do some hack to get the correct value
|
||||
// TODO: find the correct way to tackle this
|
||||
if v == big.zero_int {
|
||||
return [u8(0x00)]
|
||||
}
|
||||
// todo: proper check of 0 bytes length
|
||||
if v.bit_len() == 0 {
|
||||
return [u8(0x00)]
|
||||
}
|
||||
// otherwise, we use v.bytes() directly
|
||||
b, _ := v.bytes()
|
||||
return b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// from_i64 creates new a ASN.1 Integer from i64 v.
|
||||
pub fn Integer.from_i64(v i64) Integer {
|
||||
return Integer{
|
||||
value: IntValue(v)
|
||||
}
|
||||
}
|
||||
|
||||
// from_int creates a new ASN.1 Integer from int v.
|
||||
pub fn Integer.from_int(v int) Integer {
|
||||
return Integer{
|
||||
value: IntValue(i64(v))
|
||||
}
|
||||
}
|
||||
|
||||
// from_bigint creates a new ASN.1 Integer from big.Integer b
|
||||
pub fn Integer.from_bigint(b big.Integer) Integer {
|
||||
return Integer{
|
||||
value: IntValue(b)
|
||||
}
|
||||
}
|
||||
|
||||
// from_string creates a new ASN.1 Integer from decimal string s.
|
||||
// If your string value is below max_i64, use from_i64 instead
|
||||
pub fn Integer.from_string(s string) !Integer {
|
||||
v := big.integer_from_string(s)!
|
||||
return Integer{
|
||||
value: IntValue(v)
|
||||
}
|
||||
}
|
||||
|
||||
// from_hex creates a new ASN.1 Integer from hex string in x
|
||||
// where x is a valid hex string without `0x` prefix.
|
||||
// If your string value is below max_i64, use from_i64 instead
|
||||
pub fn Integer.from_hex(x string) !Integer {
|
||||
s := big.integer_from_radix(x, 16)!
|
||||
return Integer{
|
||||
value: IntValue(s)
|
||||
}
|
||||
}
|
||||
|
||||
// from_bytes creates a new ASN.1 Integer from bytes array in b.
|
||||
// Its try to parse bytes as in two's complement form.
|
||||
fn Integer.from_bytes(b []u8) !Integer {
|
||||
return Integer.unpack_from_twoscomplement_bytes(b)!
|
||||
}
|
||||
|
||||
// unpack_from_twoscomplement_bytes parses the bytes in b into the Integer
|
||||
// value in the big-endian two's complement way. If b[0]&80 != 0, the number
|
||||
// is negative. If b is empty it would be error.
|
||||
fn Integer.unpack_from_twoscomplement_bytes(b []u8) !Integer {
|
||||
// FIXME: should we return error instead ?
|
||||
if b.len == 0 {
|
||||
return error('Integer: null bytes')
|
||||
}
|
||||
if b.len > 7 {
|
||||
mut num := big.integer_from_bytes(b)
|
||||
// negative number
|
||||
if b.len > 0 && b[0] & 0x80 > 0 {
|
||||
sub := big.one_int.left_shift(u32(b.len) * 8)
|
||||
num -= sub
|
||||
}
|
||||
|
||||
return Integer{
|
||||
value: IntValue(num)
|
||||
}
|
||||
}
|
||||
// use i64
|
||||
val := read_i64(b)!
|
||||
res := Integer.from_i64(val)
|
||||
return res
|
||||
}
|
||||
|
||||
// bytes return underlying bytes array
|
||||
fn (v Integer) bytes() []u8 {
|
||||
return v.value.bytes()
|
||||
}
|
||||
|
||||
// bytes_len returns underlying bytes length
|
||||
fn (v Integer) bytes_len() int {
|
||||
b := v.value.bytes()
|
||||
return b.len
|
||||
}
|
||||
|
||||
// tag returns the tag of Integer type element
|
||||
pub fn (v Integer) tag() Tag {
|
||||
return default_integer_tag
|
||||
}
|
||||
|
||||
// payload returns the payload of Integer type element.
|
||||
pub fn (v Integer) payload() ![]u8 {
|
||||
bytes, _ := v.pack_into_twoscomplement_form()!
|
||||
return bytes
|
||||
}
|
||||
|
||||
// pack_into_twoscomplement_form serialize Integer in two's-complement rules.
|
||||
// - The integer value contains the encoded integer if it is positive, or its two's complement if it is negative.
|
||||
// - If the integer is positive but the high order bit is set to 1, a leading 0x00 is added to the content
|
||||
// to indicate that the number is not negative.
|
||||
// - If the number is negative after applying two's-complement rules, and the most-significant-bit of the
|
||||
// the high order bit of the bytes results isn't set, pad it with 0xff in order to keep the number negative.
|
||||
fn (v Integer) pack_into_twoscomplement_form() !([]u8, int) {
|
||||
match v.value {
|
||||
i64 {
|
||||
val := v.value as i64
|
||||
mut bytes := i64_to_bytes(val)
|
||||
return bytes, bytes.len
|
||||
}
|
||||
big.Integer {
|
||||
match v.value.signum {
|
||||
0 {
|
||||
return [u8(0x00)], 1
|
||||
}
|
||||
1 {
|
||||
mut b := v.bytes()
|
||||
// handle the zero issues
|
||||
if b.len == 0 {
|
||||
return [u8(0x00)], 1
|
||||
}
|
||||
// If the integer is positive but the high order bit is set to 1, a leading 0x00 is added
|
||||
// to the content to indicate that the number is not negative
|
||||
if b[0] & 0x80 > 0 {
|
||||
b.prepend(u8(0x00))
|
||||
}
|
||||
return b, b.len
|
||||
}
|
||||
-1 {
|
||||
// A negative number has to be converted to two's-complement form.
|
||||
// by invert the number and then subtract it with big(1), or with other mean
|
||||
// Flip all of the bits in the value and then add one to the resulting value.
|
||||
// If the most-significant-bit isn't set then we'll need to pad the
|
||||
// beginning with 0xff in order to keep the number negative.
|
||||
negv := v.value.neg()
|
||||
negvminus1 := negv - big.one_int
|
||||
mut bytes, _ := negvminus1.bytes()
|
||||
for i, _ in bytes {
|
||||
bytes[i] ^= 0xff
|
||||
}
|
||||
if bytes.len == 0 || bytes[0] & 0x80 == 0 {
|
||||
bytes.prepend(u8(0xff))
|
||||
}
|
||||
return bytes, bytes.len
|
||||
}
|
||||
else {
|
||||
return error('should unreachable')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// equal do checking if integer n was equal to integer m.
|
||||
// ISSUE?: There are some issues when compared n == m directly,
|
||||
// its fails even internally its a same, so we provide and use equality check
|
||||
pub fn (n Integer) equal(m Integer) bool {
|
||||
nbytes := n.bytes()
|
||||
mbytes := m.bytes()
|
||||
// todo: check sign equality
|
||||
// m.tag() == n.tag() by definition, no need to check
|
||||
return subtle.constant_time_compare(nbytes, mbytes) == 1
|
||||
}
|
||||
|
||||
// Integer.unpack_and_validate deserializes bytes in b into Integer
|
||||
// in two's complement way and perform validation on this bytes to
|
||||
// meet der requirement.
|
||||
fn Integer.unpack_and_validate(b []u8) !Integer {
|
||||
if !valid_bytes(b, true) {
|
||||
return error('Integer: check return false')
|
||||
}
|
||||
ret := Integer.unpack_from_twoscomplement_bytes(b)!
|
||||
return ret
|
||||
}
|
||||
|
||||
// as_bigint casts Integer value to big.Integer or error on fails.
|
||||
pub fn (v Integer) as_bigint() !big.Integer {
|
||||
if v.value is big.Integer {
|
||||
val := v.value as big.Integer
|
||||
return val
|
||||
}
|
||||
return error('Integer not hold big.Integer type')
|
||||
}
|
||||
|
||||
// as_i64 casts Integer value to i64 value or error on fails.
|
||||
pub fn (v Integer) as_i64() !i64 {
|
||||
if v.value is i64 {
|
||||
val := v.value as i64
|
||||
return val
|
||||
}
|
||||
return error('Integer not hold i64 type')
|
||||
}
|
||||
|
||||
// parse tries to read and parse into Integer type or return error on fails.
|
||||
fn Integer.parse(mut p Parser) !Integer {
|
||||
tag := p.read_tag()!
|
||||
if !tag.equal(default_integer_tag) {
|
||||
return error('Get unexected non Integer tag')
|
||||
}
|
||||
length := p.read_length()!
|
||||
if length < 1 {
|
||||
return error('Get length < 1 for Integer length')
|
||||
}
|
||||
bytes := p.read_bytes(length)!
|
||||
ret := Integer.from_bytes(bytes)!
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// decode tries to decode bytes array into Integer type or error on fails
|
||||
fn Integer.decode(bytes []u8) !(Integer, int) {
|
||||
return Integer.decode_with_rule(bytes, 0, .der)!
|
||||
}
|
||||
|
||||
// decode_with_rule tries to decode bytes back into ASN.1 Integer.
|
||||
// Its accepts `loc` params, the location (offset) within bytes where the unpack start from.
|
||||
// If not sure set to 0 to drive unpacking and rule of `Encodingrule`, currently only support`.der`.
|
||||
fn Integer.decode_with_rule(bytes []u8, loc int, rule EncodingRule) !(Integer, int) {
|
||||
if bytes.len < 3 {
|
||||
return error('Integer: bad bytes length')
|
||||
}
|
||||
tag, length_pos := Tag.decode_with_rule(bytes, loc, rule)!
|
||||
if !tag.equal(default_integer_tag) {
|
||||
return error('Get unexpected Integer tag')
|
||||
}
|
||||
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
|
||||
payload := if length == 0 {
|
||||
[]u8{}
|
||||
} else {
|
||||
if content_pos + length > bytes.len {
|
||||
return error('Not enought bytes to read on')
|
||||
}
|
||||
unsafe { bytes[content_pos..content_pos + length] }
|
||||
}
|
||||
|
||||
// buf := trim_bytes(payload)!
|
||||
next := content_pos + length
|
||||
result := Integer.from_bytes(payload)!
|
||||
|
||||
return result, next
|
||||
}
|
||||
|
||||
// Utility function
|
||||
//
|
||||
fn is_highest_bit_set(src []u8) bool {
|
||||
if src.len > 0 {
|
||||
return src[0] & 0x80 == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn trim_bytes(src []u8) ![]u8 {
|
||||
if src.len == 0 {
|
||||
return error('bad src')
|
||||
}
|
||||
// TODO: removes prepended bytes when its meet criteria
|
||||
// positive value but its prepended with 0x00
|
||||
if src.len > 1 && src[0] == 0x00 && src[1] & 0x80 > 0 {
|
||||
bytes := src[1..]
|
||||
return bytes
|
||||
}
|
||||
// TODO: how to do with multiples 0xff
|
||||
if src.len > 1 && src[0] == 0xff && src[1] & 0x80 == 0 {
|
||||
bytes := src[1..]
|
||||
return bytes
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
// length_i64 gets bytes length needed to reperesent this i64 value
|
||||
fn length_i64(val i64) int {
|
||||
mut i := val
|
||||
mut n := 1
|
||||
|
||||
for i > 127 {
|
||||
n++
|
||||
i >>= 8
|
||||
}
|
||||
|
||||
for i < -128 {
|
||||
n++
|
||||
i >>= 8
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// i64_to_bytes transforms i64 value into bytes representation
|
||||
fn i64_to_bytes(i i64) []u8 {
|
||||
mut n := length_i64(i)
|
||||
mut dst := []u8{len: n}
|
||||
for j := 0; j < n; j++ {
|
||||
dst[j] = u8(i >> u32((n - 1 - j) * 8))
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// read_i64 read src as signed i64
|
||||
fn read_i64(src []u8) !i64 {
|
||||
if !valid_bytes(src, true) {
|
||||
return error('i64 check return false')
|
||||
}
|
||||
mut ret := i64(0)
|
||||
|
||||
if src.len > 8 {
|
||||
return error('too large integer')
|
||||
}
|
||||
for i := 0; i < src.len; i++ {
|
||||
ret <<= 8
|
||||
ret |= i64(src[i])
|
||||
}
|
||||
|
||||
ret <<= 64 - u8(src.len) * 8
|
||||
ret >>= 64 - u8(src.len) * 8
|
||||
|
||||
// try to serialize back, and check its matching original one
|
||||
// and gives a warning when its not match.
|
||||
|
||||
dst := i64_to_bytes(ret)
|
||||
if dst != src {
|
||||
eprintln('maybe integer bytes not in shortest form')
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// i32 handling
|
||||
//
|
||||
// read_i32 read from bytes
|
||||
fn read_i32(src []u8) !int {
|
||||
if !valid_bytes(src, true) {
|
||||
return error('i32 check return false')
|
||||
}
|
||||
|
||||
ret := read_i64(src)!
|
||||
if ret != i64(int(ret)) {
|
||||
return error('integer too large')
|
||||
}
|
||||
|
||||
return int(ret)
|
||||
}
|
||||
|
||||
fn length_i32(v i32) int {
|
||||
return length_i64(i64(v))
|
||||
}
|
||||
|
||||
fn i32_to_bytes(v i32) []u8 {
|
||||
return i64_to_bytes(i64(v))
|
||||
}
|
||||
Reference in new issue
Block a user