| 1 | // Copyright (c) 2022, 2024 blackshirt. All rights reserved. |
| 2 | // Use of this source code is governed by a MIT License |
| 3 | // that can be found in the LICENSE file. |
| 4 | module asn1 |
| 5 | |
| 6 | struct OidWriteTest { |
| 7 | inp []int |
| 8 | exp []u8 |
| 9 | err IError |
| 10 | } |
| 11 | |
| 12 | fn assert_no_error(err IError) { |
| 13 | assert err.str() == 'none' |
| 14 | } |
| 15 | |
| 16 | fn test_write_oid() ! { |
| 17 | dt := [ |
| 18 | OidWriteTest{[], [], error('ObjectIdentifier: bad oid int array')}, // empty arc |
| 19 | OidWriteTest{[0], [u8(0x00)], error('ObjectIdentifier: bad oid int array')}, // only root arc |
| 20 | OidWriteTest{[0, 0], [u8(0x00)], none}, |
| 21 | OidWriteTest{[3, 0], [u8(0x00)], error('ObjectIdentifier: bad oid int array')}, // first arc, 3 is not allowed value |
| 22 | OidWriteTest{[0, 40], [u8(0x00)], error('ObjectIdentifier: bad oid int array')}, // second arc, 40 is not allowed (its should <= 39) |
| 23 | OidWriteTest{[1, 40], [u8(0x00)], error('ObjectIdentifier: bad oid int array')}, // second arc, 40 is not allowed (its should <= 39) |
| 24 | OidWriteTest{[1, 2], [u8(0x2a)], none}, |
| 25 | OidWriteTest{[2, 5], [u8(0x55)], none}, |
| 26 | OidWriteTest{[1, 2, 840], [u8(0x2a), 0x86, 0x48], none}, |
| 27 | OidWriteTest{[1, 2, 840, 113549], [u8(0x2a), 0x86, 0x48, 0x86, 0xF7, 0x0D], none}, |
| 28 | OidWriteTest{[1, 2, 840, 113549, 1], [u8(0x2a), 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01], none}, |
| 29 | ] |
| 30 | |
| 31 | for item in dt { |
| 32 | oid := ObjectIdentifier.from_ints(item.inp) or { |
| 33 | assert err == item.err |
| 34 | continue |
| 35 | } |
| 36 | |
| 37 | assert_no_error(item.err) |
| 38 | dst := oid.payload()! |
| 39 | |
| 40 | assert dst == item.exp |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | struct BuildOidTest { |
| 45 | inp []int |
| 46 | out ObjectIdentifier |
| 47 | err IError |
| 48 | } |
| 49 | |
| 50 | fn test_oid_from_ints() ! { |
| 51 | td := [ |
| 52 | BuildOidTest{[1, 2], ObjectIdentifier{ |
| 53 | value: [1, 2] |
| 54 | }, none}, |
| 55 | BuildOidTest{[1, 2, 3], ObjectIdentifier{ |
| 56 | value: [1, 2, 3] |
| 57 | }, none}, |
| 58 | BuildOidTest{[1, 4, 4], ObjectIdentifier{ |
| 59 | value: [1, 4, 4] |
| 60 | }, none}, |
| 61 | BuildOidTest{[1, 39, 6, 256], ObjectIdentifier{ |
| 62 | value: [1, 39, 6, 256] |
| 63 | }, none}, |
| 64 | // second >= 40 when first < 2 not allowed |
| 65 | BuildOidTest{[1, 40, 4], ObjectIdentifier{ |
| 66 | value: [1, 40, 4] |
| 67 | }, error('ObjectIdentifier: bad oid int array')}, |
| 68 | // first value bigger than 2 was not allowed |
| 69 | BuildOidTest{[4, 5, 6], ObjectIdentifier{ |
| 70 | value: [4, 5, 6] |
| 71 | }, error('ObjectIdentifier: bad oid int array')}, |
| 72 | // second value >= 40 was not allowed when first < 2 |
| 73 | BuildOidTest{[1, 40, 6], ObjectIdentifier{ |
| 74 | value: [1, 40, 6] |
| 75 | }, error('ObjectIdentifier: bad oid int array')}, |
| 76 | // when the first arc is 2, the second arc is not limited to 0..39 |
| 77 | BuildOidTest{[2, 50, 6], ObjectIdentifier{ |
| 78 | value: [2, 50, 6] |
| 79 | }, none}, |
| 80 | // still valid: value is well below the i32 limit checked by from_ints |
| 81 | BuildOidTest{[1, 4, 863123683], ObjectIdentifier{ |
| 82 | value: [1, 4, 863123683] |
| 83 | }, none}, |
| 84 | BuildOidTest{[4, 0xab, 4], ObjectIdentifier{ |
| 85 | value: [4, 0xab, 4] |
| 86 | }, error('ObjectIdentifier: bad oid int array')}, |
| 87 | BuildOidTest{[4, 0x0c, 4], ObjectIdentifier{ |
| 88 | value: [4, 0x0c, 4] |
| 89 | }, error('ObjectIdentifier: bad oid int array')}, |
| 90 | BuildOidTest{[2], ObjectIdentifier{ |
| 91 | value: [2] |
| 92 | }, error('ObjectIdentifier: bad oid int array')}, |
| 93 | ] |
| 94 | for c in td { |
| 95 | s := ObjectIdentifier.from_ints(c.inp) or { |
| 96 | assert err == c.err |
| 97 | continue |
| 98 | } |
| 99 | assert_no_error(c.err) |
| 100 | assert s == c.out |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | struct OidStrTest { |
| 105 | inp string |
| 106 | out ObjectIdentifier |
| 107 | err IError |
| 108 | } |
| 109 | |
| 110 | fn test_oid_from_string() ! { |
| 111 | td := [ |
| 112 | OidStrTest{'1.2.840.113549', ObjectIdentifier{ |
| 113 | value: [1, 2, 840, 113549] |
| 114 | }, none}, |
| 115 | OidStrTest{'1.3.6.1.3', ObjectIdentifier{ |
| 116 | value: [1, 3, 6, 1, 3] |
| 117 | }, none}, |
| 118 | OidStrTest{'1.2', ObjectIdentifier{ |
| 119 | value: [1, 2] |
| 120 | }, none}, |
| 121 | OidStrTest{'1.4.4', ObjectIdentifier{ |
| 122 | value: [1, 4, 4] |
| 123 | }, none}, |
| 124 | OidStrTest{'1.4.x', ObjectIdentifier{ |
| 125 | value: [1, 4, 4] |
| 126 | }, error('common_parse_uint: syntax error x')}, // invalid char |
| 127 | OidStrTest{'4.4.4', ObjectIdentifier{ |
| 128 | value: [4, 4, 4] |
| 129 | }, error('ObjectIdentifier: bad oid string')}, |
| 130 | OidStrTest{'1.4.863123683', ObjectIdentifier{ |
| 131 | value: [1, 4, 863123683] |
| 132 | }, none}, |
| 133 | OidStrTest{'4.ab.4', ObjectIdentifier{ |
| 134 | value: [4, 0xab, 4] |
| 135 | }, error('common_parse_uint: syntax error ab')}, // invalid char |
| 136 | OidStrTest{'4.c.4', ObjectIdentifier{ |
| 137 | value: [4, 0x0c, 4] |
| 138 | }, error('common_parse_uint: syntax error c')}, // invalid char |
| 139 | OidStrTest{'2', ObjectIdentifier{ |
| 140 | value: [2] |
| 141 | }, error('ObjectIdentifier: bad string oid length')}, |
| 142 | ] |
| 143 | for s in td { |
| 144 | v := ObjectIdentifier.new(s.inp) or { |
| 145 | assert err == s.err |
| 146 | continue |
| 147 | } |
| 148 | assert_no_error(s.err) |
| 149 | assert v == s.out |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | fn test_serialize_oid_basic() { |
| 154 | // https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/object-identifier.html |
| 155 | inp := [1, 0, 8571, 2, 1] |
| 156 | exp := [u8(6), 5, 0x28, 0xC2, 0x7B, 0x02, 0x01] |
| 157 | oid := ObjectIdentifier.from_ints(inp)! |
| 158 | |
| 159 | out := encode(oid)! |
| 160 | |
| 161 | assert out == exp |
| 162 | } |
| 163 | |
| 164 | struct OidSerializeTest { |
| 165 | inp []int |
| 166 | exp []u8 |
| 167 | err IError |
| 168 | } |
| 169 | |
| 170 | fn test_serialize_decode_oid() { |
| 171 | td := [ |
| 172 | OidSerializeTest{[0, 0], [u8(0x06), 0x01, 0x00], none}, |
| 173 | OidSerializeTest{[1, 2, 3], [u8(0x06), 0x02, 0x2a, 0x03], none}, |
| 174 | OidSerializeTest{[1, 3, 6, 1, 3], [u8(0x06), 0x04, 0x2b, 0x06, 1, 3], none}, |
| 175 | OidSerializeTest{[2, 999, 1234], [u8(0x06), 0x04, 0x88, 0x37, 0x89, 0x52], none}, |
| 176 | OidSerializeTest{[2, 999, 3], [u8(0x06), 0x03, 0x88, 0x37, 0x03], none}, // Example of ITU-T X.690 |
| 177 | // from https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier |
| 178 | OidSerializeTest{[1, 3, 6, 1, 4, 1, 311, 21, 20], [u8(0x06), 0x09, 0x2b, 0x06, 0x01, 0x04, |
| 179 | 0x01, 0x82, 0x37, 0x15, 0x14], none}, |
| 180 | // from rust-asn1 test data |
| 181 | OidSerializeTest{[1, 2, 840, 113549], [u8(0x06), 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d], none}, |
| 182 | OidSerializeTest{[1, 2, 3, 4], [u8(0x06), 0x03, 0x2a, 0x03, 0x04], none}, |
| 183 | OidSerializeTest{[1, 2, 840, 133549, 1, 1, 5], [u8(0x06), 0x09, 0x2a, 0x86, 0x48, 0x88, |
| 184 | 0x93, 0x2d, 0x01, 0x01, 0x05], none}, |
| 185 | OidSerializeTest{[2, 100, 3], [u8(0x06), 0x03, 0x81, 0x34, 0x03], none}, |
| 186 | OidSerializeTest{[1, 100, 3], [u8(0x06), 0x03, 0x81, 0x34, 0x03], error('ObjectIdentifier: bad oid int array')}, |
| 187 | OidSerializeTest{[4, 100, 3], [u8(0x06), 0x03, 0x81, 0x34, 0x03], error('ObjectIdentifier: bad oid int array')}, |
| 188 | ] |
| 189 | for t in td { |
| 190 | // dump(t.inp) |
| 191 | oid := ObjectIdentifier.from_ints(t.inp) or { |
| 192 | assert err == t.err |
| 193 | continue |
| 194 | } |
| 195 | out := encode(oid) or { |
| 196 | assert err == t.err |
| 197 | continue |
| 198 | } |
| 199 | |
| 200 | assert_no_error(t.err) |
| 201 | assert out == t.exp |
| 202 | // dump(out) |
| 203 | // decode back |
| 204 | oidback, next := ObjectIdentifier.decode(out)! |
| 205 | |
| 206 | assert oidback.tag().tag_number() == int(TagType.oid) |
| 207 | assert oidback == oid |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | fn test_oid_encode_decode() ! { |
| 212 | inp := '1.2.840.113549' |
| 213 | |
| 214 | src := ObjectIdentifier.new(inp)! |
| 215 | |
| 216 | out := encode(src)! |
| 217 | exp := [u8(0x06), 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d] |
| 218 | |
| 219 | assert out == exp |
| 220 | |
| 221 | oidback, _ := ObjectIdentifier.decode(out)! |
| 222 | |
| 223 | assert oidback.str() == '${inp}' |
| 224 | assert oidback.tag().tag_number() == 6 |
| 225 | } |
| 226 | |
| 227 | fn test_tc21_long_format_of_oid_encoding_should_error_in_der() ! { |
| 228 | data := [u8(0x06), 0x06, 0x80, 0x80, 0x51, 0x80, 0x80, 0x01] |
| 229 | |
| 230 | _, _ := ObjectIdentifier.decode(data) or { |
| 231 | assert err == error('integer is not minimaly encoded') |
| 232 | return |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | fn test_tc22_too_big_value_oid() ! { |
| 237 | data := [u8(0x06), 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, |
| 238 | 0x85, 0x03, 0x02, 0x02, 0x03] |
| 239 | |
| 240 | _, _ := ObjectIdentifier.decode(data) or { |
| 241 | assert err == error('integer is not minimaly encoded') |
| 242 | return |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | // Taken from https://letsencrypt.org/id/docs/a-warm-welcome-to-asn1-and-der/ |
| 247 | // |
| 248 | // OID 1.2.840.113549.1.1.11 (representing sha256WithRSAEncryption) is encoded like so: |
| 249 | // 06 09 2a 86 48 86 f7 0d 01 01 0b |
| 250 | fn test_sha256withrsaencryption_oid() ! { |
| 251 | oid := ObjectIdentifier.new('1.2.840.113549.1.1.11')! |
| 252 | expected := [u8(0x06), 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b] |
| 253 | |
| 254 | out := encode(oid)! |
| 255 | assert out == expected |
| 256 | |
| 257 | // decode back |
| 258 | ob, _ := ObjectIdentifier.decode(out)! |
| 259 | assert ob.equal(oid) |
| 260 | } |
| 261 | |