v/vlib/x/encoding/asn1/integer.v
2025-05-13 22:05:22 +03:00

448 lines
12 KiB
V
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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))
}