Skip to content

Commit 228fc29

Browse files
committed
Add openssl support in univers
- closes #36 - For `OpenSSL-FIPS Module` see #41 Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent 7206c1f commit 228fc29

3 files changed

Lines changed: 236 additions & 0 deletions

File tree

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: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# Visit https://aboutcode.org and https://github.com/nexB/univers for support and download.
66

77
from functools import total_ordering
8+
import re
89

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