v2 / vlib / x / encoding / asn1 / time.v
364 lines · 304 sloc · 9.77 KB · 3d60410b605d001e54f280070d5f952da9de1112
Raw
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.
4module asn1
5
6import time
7
8// default_utctime_tag is the default tag of ASN.1 UTCTIME type.
9pub const default_utctime_tag = Tag{.universal, false, int(TagType.utctime)}
10// default_generalizedtime_tag is the default tag of ASN.1 GENERALIZEDTIME type.
11pub const default_generalizedtime_tag = Tag{.universal, false, int(TagType.generalizedtime)}
12
13const default_utctime_format = 'YYMMDDHHmmssZ'
14const default_genztime_format = 'YYYYMMDDHHmmssZ'
15
16// ASN.1 UNIVERSAL CLASS OF UTCTIME TYPE.
17//
18// For this time, UtcTime represented by simple string with format "YYMMDDhhmmssZ"
19// - the six digits YYMMDD where YY is the two low-order digits of the Christian year,
20// (RFC 5280 defines it as a range from 1950 to 2049 for X.509), MM is the month
21// (counting January as 01), and DD is the day of the month (01 to 31).
22// - the four digits hhmm where hh is hour (00 to 23) and mm is minutes (00 to 59); (SEE NOTE BELOW)
23// - the six digits hhmmss where hh and mm are as in above, and ss is seconds (00 to 59);
24// - the character Z;
25// - one of the characters + or -, followed by hhmm, where hh is hour and mm is minutes (NOT SUPPORTED)
26//
27// NOTE
28// -----
29// - Restrictions employed by DER, the encoding shall terminate with "Z".
30// - The seconds element shall always be present, and DER (along with RFC 5280) specify that seconds must be present,
31// - Fractional seconds must not be present.
32//
33// TODO:
34// - check for invalid representation of date and hhmmss part.
35// - represented UtcTime in time.Time
36pub struct UtcTime {
37pub:
38 value string
39}
40
41// new_utctime creates a new UtcTime element from string s.
42pub fn UtcTime.new(s string) !UtcTime {
43 valid := validate_utctime(s)!
44
45 if !valid {
46 return error('UtcTime: fail on validate utctime')
47 }
48 return UtcTime{
49 value: s
50 }
51}
52
53// from_time creates a new UtcTime element from standard `time.Time` in UTC time format.
54pub fn UtcTime.from_time(t time.Time) !UtcTime {
55 // changes into utc time
56 utime := t.local_to_utc()
57 s := utime.custom_format(default_utctime_format) // 20241113060446+0
58 // Its rather a hack, not efieient as should be.
59 // TODO: make it better
60 str := s.split_any('+-')
61 val := str[0] + 'Z'
62 utc := UtcTime.new(val)!
63 return utc
64}
65
66fn UtcTime.from_bytes(b []u8) !UtcTime {
67 return UtcTime.new(b.bytestr())!
68}
69
70fn (utc UtcTime) str() string {
71 if utc.value.len == 0 {
72 return 'UtcTime (<empty>)'
73 }
74 return 'UtcTime (${utc.value})'
75}
76
77// tag retursn the tag of the UtcTime element.
78pub fn (utc UtcTime) tag() Tag {
79 return default_utctime_tag
80}
81
82// payload returns the payload of the UtcTime element.
83pub fn (utc UtcTime) payload() ![]u8 {
84 return utc.payload_with_rule(.der)!
85}
86
87// into_utctime turns this UtcTime into corresponding UTC time.
88pub fn (utc UtcTime) into_utctime() !time.Time {
89 valid := validate_utctime(utc.value)!
90
91 if !valid {
92 return error('UtcTime: fail on validate utctime value')
93 }
94
95 st := time.parse_format(utc.value, default_utctime_format)!
96 utime := st.local_to_utc()
97
98 return utime
99}
100
101fn (utc UtcTime) payload_with_rule(_rule EncodingRule) ![]u8 {
102 valid := validate_utctime(utc.value)!
103
104 if !valid {
105 return error('UtcTime: fail on validate utctime')
106 }
107
108 return utc.value.bytes()
109}
110
111fn UtcTime.parse(mut p Parser) !UtcTime {
112 tag := p.read_tag()!
113 if !tag.equal(default_utctime_tag) {
114 return error('Bad UtcTime tag')
115 }
116 length := p.read_length()!
117 bytes := p.read_bytes(length)!
118
119 res := UtcTime.from_bytes(bytes)!
120
121 return res
122}
123
124// UtcTime.decode tries to decode bytes into UtcTime with DER rule
125fn UtcTime.decode(src []u8) !(UtcTime, int) {
126 return UtcTime.decode_with_rule(src, .der)!
127}
128
129fn UtcTime.decode_with_rule(bytes []u8, rule EncodingRule) !(UtcTime, int) {
130 tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
131 if !tag.equal(default_utctime_tag) {
132 return error('Unexpected non-utctime tag')
133 }
134 length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
135 content := if length == 0 {
136 []u8{}
137 } else {
138 if content_pos >= bytes.len || content_pos + length > bytes.len {
139 return error('UtcTime: truncated payload bytes')
140 }
141 unsafe { bytes[content_pos..content_pos + length] }
142 }
143
144 utc := UtcTime.from_bytes(content)!
145 next := content_pos + length
146
147 return utc, next
148}
149
150// utility function for UtcTime
151//
152fn validate_utctime(s string) !bool {
153 if !basic_utctime_check(s) {
154 return false
155 }
156 // read contents
157 src := s.bytes()
158 mut pos := 0
159 mut year, mut month, mut day := u16(0), u8(0), u8(0)
160 mut hour, mut minute, mut second := u8(0), u8(0), u8(0)
161
162 // UtcTime only encodes times prior to 2050
163 year, pos = read_2_digits(src, pos)!
164 year = u16(year)
165 if year >= 50 {
166 year = 1900 + year
167 } else {
168 year = 2000 + year
169 }
170
171 month, pos = read_2_digits(src, pos)!
172 day, pos = read_2_digits(src, pos)!
173
174 if !validate_date(year, month, day) {
175 return false
176 }
177
178 // hhmmss parts
179 hour, pos = read_2_digits(src, pos)!
180 minute, pos = read_2_digits(src, pos)!
181 second, pos = read_2_digits(src, pos)!
182
183 if hour > 23 || minute > 59 || second > 59 {
184 return false
185 }
186 // assert pos == src.len - 1
187 if src[pos] != 0x5A {
188 return false
189 }
190 return true
191}
192
193fn basic_utctime_check(s string) bool {
194 return s.len == 13 && valid_time_contents(s)
195}
196
197fn valid_time_contents(s string) bool {
198 return s.ends_with('Z') && s.contains_any('0123456789')
199}
200
201// ASN.1 UNIVERSAL CLASS OF GENERALIZEDTIME TYPE.
202//
203// In DER Encoding scheme, GeneralizedTime should :
204// - The encoding shall terminate with a "Z"
205// - The seconds element shall always be present
206// - The fractional-seconds elements, if present, shall omit all trailing zeros;
207// - if the elements correspond to 0, they shall be wholly omitted, and the decimal point element also shall be omitted
208//
209// GeneralizedTime values MUST be:
210// - expressed in Greenwich Mean Time (Zulu) and MUST include seconds
211// (i.e., times are `YYYYMMDDHHMMSSZ`), even where the number of seconds
212// is zero.
213// - GeneralizedTime values MUST NOT include fractional seconds.
214pub struct GeneralizedTime {
215pub:
216 value string
217}
218
219// GeneralizedTime.new creates a new GeneralizedTime from string s
220pub fn GeneralizedTime.new(s string) !GeneralizedTime {
221 valid := validate_generalizedtime(s)!
222 if !valid {
223 return error('GeneralizedTime: failed on validate')
224 }
225 return GeneralizedTime{
226 value: s
227 }
228}
229
230// from_time creates GeneralizedTime element from standard `time.Time` (as an UTC time).
231pub fn GeneralizedTime.from_time(t time.Time) !GeneralizedTime {
232 u := t.local_to_utc()
233 s := u.custom_format(default_genztime_format)
234 // adds support directly from time.Time
235 src := s.split_any('+-')
236 val := src[0] + 'Z'
237 gt := GeneralizedTime.new(val)!
238
239 return gt
240}
241
242// into_utctime turns this GeneralizedTime into corresponding UTC time.
243pub fn (gt GeneralizedTime) into_utctime() !time.Time {
244 valid := validate_generalizedtime(gt.value)!
245
246 if !valid {
247 return error('GeneralizedTime: fail on validate value')
248 }
249
250 st := time.parse_format(gt.value, default_genztime_format)!
251 utime := st.local_to_utc()
252
253 return utime
254}
255
256// The tag of GeneralizedTime element.
257pub fn (gt GeneralizedTime) tag() Tag {
258 return default_generalizedtime_tag
259}
260
261// The payload of GeneralizedTime element.
262pub fn (gt GeneralizedTime) payload() ![]u8 {
263 valid := validate_generalizedtime(gt.value)!
264 if !valid {
265 return error('GeneralizedTime: failed on validate')
266 }
267 return gt.value.bytes()
268}
269
270fn GeneralizedTime.from_bytes(b []u8) !GeneralizedTime {
271 return GeneralizedTime.new(b.bytestr())!
272}
273
274// GeneralizedTime.parse tries to parse throught ongoing Parser into GeneralizedTime
275fn GeneralizedTime.parse(mut p Parser) !GeneralizedTime {
276 tag := p.read_tag()!
277 if !tag.equal(default_generalizedtime_tag) {
278 return error('Bad GeneralizedTime tag')
279 }
280 length := p.read_length()!
281 bytes := p.read_bytes(length)!
282
283 res := GeneralizedTime.from_bytes(bytes)!
284
285 return res
286}
287
288fn GeneralizedTime.decode(bytes []u8) !(GeneralizedTime, int) {
289 return GeneralizedTime.decode_with_rule(bytes, .der)!
290}
291
292fn GeneralizedTime.decode_with_rule(bytes []u8, rule EncodingRule) !(GeneralizedTime, int) {
293 tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
294 if !tag.equal(default_generalizedtime_tag) {
295 return error('Get bad GeneralizedTime tag')
296 }
297 length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
298 content := if length == 0 {
299 []u8{}
300 } else {
301 if content_pos >= bytes.len || content_pos + length > bytes.len {
302 return error('GeneralizedTime: truncated payload bytes')
303 }
304 unsafe { bytes[content_pos..content_pos + length] }
305 }
306
307 gtc := GeneralizedTime.from_bytes(content)!
308 next := content_pos + length
309
310 return gtc, next
311}
312
313// utility function for GeneralizedTime
314// TODO: more clear and concise validation check
315
316fn min_generalizedtime_length(s string) bool {
317 // minimum length without fractional element
318 return s.len >= 15
319}
320
321fn generalizedtime_contains_fraction(s string) bool {
322 // contains '.' part
323 return s.contains('.')
324}
325
326fn basic_generalizedtime_check(s string) bool {
327 return min_generalizedtime_length(s) && valid_time_contents(s)
328}
329
330fn validate_generalizedtime(s string) !bool {
331 if !basic_generalizedtime_check(s) {
332 return false
333 }
334 // read contents
335 src := s.bytes()
336 mut pos := 0
337 mut year, mut month, mut day := u16(0), u8(0), u8(0)
338 mut hour, mut minute, mut second := u8(0), u8(0), u8(0)
339
340 // Generalized time format was "YYYYMMDDhhmmssZ"
341 // TODO: support for second fractions part
342 year, pos = read_4_digits(src, pos)!
343 // year = u16(year)
344 month, pos = read_2_digits(src, pos)!
345 day, pos = read_2_digits(src, pos)!
346
347 if !validate_date(year, month, day) {
348 return false
349 }
350
351 // hhmmss parts
352 hour, pos = read_2_digits(src, pos)!
353 minute, pos = read_2_digits(src, pos)!
354 second, pos = read_2_digits(src, pos)!
355
356 if hour > 23 || minute > 59 || second > 59 {
357 return false
358 }
359 // assert pos == src.len - 1
360 if src[pos] != 0x5A {
361 return false
362 }
363 return true
364}
365