Skip to content

Commit 55372de

Browse files
authored
Merge pull request #42 from keshav-space/main
Add openssl support in univers
2 parents db14416 + 0ff5a9c commit 55372de

4 files changed

Lines changed: 559 additions & 0 deletions

File tree

src/univers/version_range.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,37 @@ def from_native(cls, string):
868868
return cls(constraints=constraints)
869869

870870

871+
class OpensslVersionRange(VersionRange):
872+
"""
873+
Openssl version range.
874+
openssl doesn't use <,>,<= or >=
875+
For more see 'https://www.openssl.org/news/vulnerabilities.xml'
876+
877+
For example::
878+
>>> from univers.versions import OpensslVersion
879+
>>> constraints = (
880+
... VersionConstraint(version=OpensslVersion("1.0.1af")),
881+
... VersionConstraint(comparator="=", version=OpensslVersion("3.0.1")),
882+
... VersionConstraint(comparator="=", version=OpensslVersion("1.1.1nf")),
883+
... )
884+
>>> range = OpensslVersionRange(constraints=constraints)
885+
>>> assert str(range) == 'vers:openssl/1.0.1af|1.1.1nf|3.0.1'
886+
"""
887+
888+
scheme = "openssl"
889+
version_class = versions.OpensslVersion
890+
891+
@classmethod
892+
def from_native(cls, string):
893+
cleaned = remove_spaces(string).lower()
894+
constraints = []
895+
for version in cleaned.split(","):
896+
version_obj = cls.version_class(version)
897+
constraint = VersionConstraint(comparator="=", version=version_obj)
898+
constraints.append(constraint)
899+
return cls(constraints=constraints)
900+
901+
871902
def is_even(s):
872903
"""
873904
Return True if the string "s" is an even number and False if this is an odd
@@ -902,4 +933,5 @@ def is_even(s):
902933
"ebuild": EbuildVersionRange,
903934
"archlinux": ArchLinuxVersionRange,
904935
"nginx": NginxVersionRange,
936+
"openssl": OpensslVersionRange,
905937
}

src/univers/versions.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,179 @@ def __gt__(self, other):
343343
if not isinstance(other, self.__class__):
344344
return NotImplemented
345345
return gentoo.vercmp(self.value, other.value) > 0
346+
347+
348+
@attr.s(frozen=True, order=False, eq=False, hash=True)
349+
class LegacyOpensslVersion(Version):
350+
"""
351+
Represent an Legacy Openssl Version.
352+
353+
For example::
354+
>>> LegacyOpensslVersion("1.0.1f")
355+
LegacyOpensslVersion(string='1.0.1f')
356+
>>> LegacyOpensslVersion("1.0.2ac")
357+
LegacyOpensslVersion(string='1.0.2ac')
358+
>>> LegacyOpensslVersion("1.0.2a")
359+
LegacyOpensslVersion(string='1.0.2a')
360+
>>> LegacyOpensslVersion("3.0.2")
361+
Traceback (most recent call last):
362+
...
363+
univers.versions.InvalidVersion: '3.0.2' is not a valid <class 'univers.versions.LegacyOpensslVersion'>
364+
"""
365+
366+
@classmethod
367+
def is_valid(cls, string):
368+
return bool(cls.parse(string))
369+
370+
@classmethod
371+
def parse(cls, string):
372+
"""
373+
Return a four-tuple of (major, minor, build, patch) version segments where
374+
major, minor, build are integers and patch is a string possibly empty.
375+
Return False if this is not a valid LegacyOpensslVersion.
376+
377+
For example::
378+
>>> LegacyOpensslVersion.parse("1.0.1f")
379+
(1, 0, 1, 'f')
380+
>>> LegacyOpensslVersion.parse("1.0.2ac")
381+
(1, 0, 2, 'ac')
382+
>>> LegacyOpensslVersion.parse("2.0.2az")
383+
False
384+
"""
385+
386+
# All known legacy base OpenSSL versions
387+
all_legacy_base = (
388+
"0.9.1",
389+
"0.9.2",
390+
"0.9.3",
391+
"0.9.4",
392+
"0.9.5",
393+
"0.9.6",
394+
"0.9.7",
395+
"0.9.8",
396+
"1.0.0",
397+
"1.0.1",
398+
"1.0.2",
399+
"1.1.0",
400+
"1.1.1",
401+
)
402+
if not string.startswith(all_legacy_base):
403+
return False
404+
405+
segments = string.split(".")
406+
if not len(segments) == 3:
407+
return False
408+
major, minor, build = segments
409+
major = int(major)
410+
minor = int(minor)
411+
if build.isdigit():
412+
build = int(build)
413+
patch = ""
414+
else:
415+
patch = build[1:]
416+
build = int(build[0])
417+
if patch[0].isdigit():
418+
return False
419+
return major, minor, build, patch
420+
421+
@classmethod
422+
def build_value(cls, string):
423+
return cls.parse(string)
424+
425+
def __str__(self):
426+
return f"{self.value[0]}.{self.value[1]}.{self.value[2]}{self.value[3]}"
427+
428+
429+
@attr.s(frozen=True, order=False, eq=False, hash=True)
430+
class OpensslVersion(Version):
431+
"""
432+
Internally tracks two types of openssl versions
433+
- LegacyOpensslVersion for versions before version 3.0.0 such as 1.0.1g
434+
- Semver for versions from 3.0.0 and up
435+
For example::
436+
>>> old = OpensslVersion("1.1.0f")
437+
>>> new = OpensslVersion("3.0.1")
438+
>>> assert old == OpensslVersion(string="1.1.0f")
439+
>>> assert new == OpensslVersion(string="3.0.1")
440+
>>> assert old.value == LegacyOpensslVersion(string="1.1.0f")
441+
>>> assert new.value == SemverVersion(string="3.0.1")
442+
>>> OpensslVersion("1.2.4fg")
443+
Traceback (most recent call last):
444+
...
445+
univers.versions.InvalidVersion: '1.2.4fg' is not a valid <class 'univers.versions.OpensslVersion'>
446+
"""
447+
448+
@classmethod
449+
def is_valid(cls, string):
450+
return cls.is_valid_new(string) or cls.is_valid_legacy(string)
451+
452+
@classmethod
453+
def build_value(cls, string):
454+
"""
455+
Return a wrapped version "value" object depending on
456+
whether version is legacy or semver.
457+
"""
458+
if cls.is_valid_legacy(string):
459+
return LegacyOpensslVersion(string)
460+
if cls.is_valid_new(string):
461+
return SemverVersion(string)
462+
463+
@classmethod
464+
def is_valid_new(cls, string):
465+
"""
466+
Check the validity of new Openssl Version.
467+
468+
For example::
469+
>>> OpensslVersion.is_valid_new("1.0.1f")
470+
False
471+
>>> OpensslVersion.is_valid_new("3.0.0")
472+
True
473+
>>> OpensslVersion.is_valid_new("3.0.2")
474+
True
475+
"""
476+
if SemverVersion.is_valid(string):
477+
sem = semantic_version.Version.coerce(string)
478+
return sem.major >= 3
479+
480+
@classmethod
481+
def is_valid_legacy(cls, string):
482+
return LegacyOpensslVersion.is_valid(string)
483+
484+
def __eq__(self, other):
485+
if not isinstance(other, self.__class__):
486+
return NotImplemented
487+
if not isinstance(other.value, self.value.__class__):
488+
return NotImplemented
489+
return self.value.__eq__(other.value)
490+
491+
def __lt__(self, other):
492+
if not isinstance(other, self.__class__):
493+
return NotImplemented
494+
if isinstance(other.value, self.value.__class__):
495+
return self.value.__lt__(other.value)
496+
# By construction legacy version is always behind Semver
497+
return isinstance(self.value, LegacyOpensslVersion)
498+
499+
def __gt__(self, other):
500+
if not isinstance(other, self.__class__):
501+
return NotImplemented
502+
if isinstance(other.value, self.value.__class__):
503+
return self.value.__gt__(other.value)
504+
# By construction semver version is always ahead of legacy
505+
return isinstance(self.value, SemverVersion)
506+
507+
def __le__(self, other):
508+
if not isinstance(other, self.__class__):
509+
return NotImplemented
510+
if isinstance(other.value, self.value.__class__):
511+
return self.value.__le__(other.value)
512+
# version value are of diff type, then legacy one is always behind semver
513+
return isinstance(self.value, LegacyOpensslVersion)
514+
515+
def __ge__(self, other):
516+
if not isinstance(other, self.__class__):
517+
return NotImplemented
518+
if isinstance(other.value, self.value.__class__):
519+
return self.value.__ge__(other.value)
520+
# version value are of diff type, then semver one is always ahead of legacy
521+
return isinstance(self.value, SemverVersion)

0 commit comments

Comments
 (0)