Skip to content

Commit cf36b09

Browse files
committed
Add openssl support in univers
- closes #36 - For `OpenSSL-FIPS Module` see #41 Signed-off-by: Keshav Priyadarshi <[email protected]>
1 parent 7206c1f commit cf36b09

File tree

3 files changed

+235
-0
lines changed

3 files changed

+235
-0
lines changed

src/univers/version_range.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,39 @@ 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 exmaple::
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+
vers_by_native_comparators = {"=": "="}
891+
892+
@classmethod
893+
def from_native(cls, string):
894+
cleaned = remove_spaces(string).lower()
895+
constraints = set()
896+
# plain single version
897+
for clause in cleaned.split(","):
898+
version = cls.version_class(clause)
899+
constraint = VersionConstraint(comparator="=", version=version)
900+
constraints.add(constraint)
901+
return cls(constraints=list(constraints))
902+
903+
871904
def is_even(s):
872905
"""
873906
Return True if the string "s" is an even number and False if this is an odd
@@ -902,4 +935,5 @@ def is_even(s):
902935
"ebuild": EbuildVersionRange,
903936
"archlinux": ArchLinuxVersionRange,
904937
"nginx": NginxVersionRange,
938+
"openssl": OpensslVersionRange,
905939
}

src/univers/versions.py

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

tests/test_version_range.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
from univers.version_range import VersionRange
1515
from univers.version_range import RANGE_CLASS_BY_SCHEMES
1616
from univers.version_range import NpmVersionRange
17+
from univers.version_range import OpensslVersionRange
1718
from univers.versions import PypiVersion
1819
from univers.versions import RubygemsVersion
1920
from univers.versions import SemverVersion
21+
from univers.versions import OpensslVersion
2022

2123

2224
class TestVersionRange(TestCase):
@@ -233,10 +235,40 @@ def test_NpmVersionRange_from_native_with_approximately_equal_to_operator(self):
233235
version_range = NpmVersionRange.from_native(npm_range)
234236
assert version_range == expected
235237

238+
def test_OpensslVersionRange_from_native_single_legacy(self):
239+
openssl_range = "0.9.8j"
240+
expected = OpensslVersionRange(
241+
constraints=(
242+
VersionConstraint(comparator="=", version=OpensslVersion(string="0.9.8j")),
243+
)
244+
)
245+
version_range = OpensslVersionRange.from_native(openssl_range)
246+
assert version_range == expected
247+
248+
def test_OpensslVersionRange_from_native_single_new_semver(self):
249+
openssl_range = "3.0.1"
250+
expected = OpensslVersionRange(
251+
constraints=(VersionConstraint(comparator="=", version=OpensslVersion(string="3.0.1")),)
252+
)
253+
version_range = OpensslVersionRange.from_native(openssl_range)
254+
assert version_range == expected
255+
256+
def test_OpensslVersionRange_from_native_mixed(self):
257+
openssl_range = "3.0.0, 1.0.1b"
258+
expected = OpensslVersionRange(
259+
constraints=(
260+
VersionConstraint(comparator="=", version=OpensslVersion(string="1.0.1b")),
261+
VersionConstraint(comparator="=", version=OpensslVersion(string="3.0.0")),
262+
)
263+
)
264+
version_range = OpensslVersionRange.from_native(openssl_range)
265+
assert version_range == expected
266+
236267

237268
VERSION_RANGE_TESTS_BY_SCHEME = {
238269
"nginx": ["0.8.40+", "0.7.52-0.8.39", "0.9.10", "1.5.0+, 1.4.1+"],
239270
"npm": ["^1.2.9", "~3.8.2", "5.0.0 - 7.2.3", "2.1 || 2.6", "1.1.2 1.2.2", "<=2.1 >=1.1"],
271+
"openssl": ["1.1.1ak", "1.1.0", "3.0.2", "3.0.1, 0.9.7a", "1.0.2ck, 3.1.2"],
240272
}
241273

242274

0 commit comments

Comments
 (0)