Skip to content

Commit

Permalink
Add openssl support in univers
Browse files Browse the repository at this point in the history
- closes aboutcode-org#36
- For `OpenSSL-FIPS Module` see aboutcode-org#41

Signed-off-by: Keshav Priyadarshi <[email protected]>
  • Loading branch information
keshav-space committed Mar 9, 2022
1 parent 7206c1f commit 6f03cf6
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/univers/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,39 @@ def from_native(cls, string):
return cls(constraints=constraints)


class OpensslVersionRange(VersionRange):
"""
Openssl version range.
openssl doesn't use <,>,<= or >=
For more see 'https://www.openssl.org/news/vulnerabilities.xml'
For exmaple::
>>> from univers.versions import OpensslVersion
>>> constraints = (
... VersionConstraint(version=OpensslVersion("1.0.1af")),
... VersionConstraint(comparator="=", version=OpensslVersion("3.0.1")),
... VersionConstraint(comparator="=", version=OpensslVersion("1.1.1nf")),
... )
>>> range = OpensslVersionRange(constraints=constraints)
>>> assert str(range) == 'vers:openssl/1.0.1af|1.1.1nf|3.0.1'
"""

scheme = "openssl"
version_class = versions.OpensslVersion
vers_by_native_comparators = {"=": "="}

@classmethod
def from_native(cls, string):
cleaned = remove_spaces(string).lower()
constraints = set()
# plain single version
for clause in cleaned.split(","):
version = cls.version_class(clause)
constraint = VersionConstraint(comparator="=", version=version)
constraints.add(constraint)
return cls(constraints=list(constraints))


def is_even(s):
"""
Return True if the string "s" is an even number and False if this is an odd
Expand Down Expand Up @@ -902,4 +935,5 @@ def is_even(s):
"ebuild": EbuildVersionRange,
"archlinux": ArchLinuxVersionRange,
"nginx": NginxVersionRange,
"openssl": OpensslVersionRange,
}
170 changes: 170 additions & 0 deletions src/univers/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,173 @@ def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return gentoo.vercmp(self.value, other.value) > 0


@attr.s(frozen=True, order=False, eq=False, hash=True)
class LegacyOpensslVersion(Version):
"""
Represent an Legacy Openssl Version .
For example::
# 1.0.1f|0.9.7d|1.0.2ac
"""

@classmethod
def is_valid(cls, string):
return bool(cls.parse(string))

@classmethod
def parse(cls, string):

"""
Returns the tuple containig the 4 segments (i.e major, minor, build, patch) of Legacy Version,
False if not valid Legacy Openssl Version.
For example::
>>> LegacyOpensslVersion.parse("1.0.1f")
(1, 0, 1, 'f')
>>> LegacyOpensslVersion.parse("1.0.2ac")
(1, 0, 2, 'ac')
>>> LegacyOpensslVersion.parse("2.0.2az")
False
"""

# All legacy base version of openssl that ever exited/exists.
all_legacy_base = (
"0.9.1",
"0.9.2",
"0.9.3",
"0.9.4",
"0.9.5",
"0.9.6",
"0.9.7",
"0.9.8",
"1.0.0",
"1.0.1",
"1.0.2",
"1.1.0",
"1.1.1",
)
# check if starts with a valid base
if not string.startswith(all_legacy_base):
return False

segments = string.split(".")
if not len(segments) == 3:
return False
major, minor, build = segments
major = int(major)
minor = int(minor)
if build.isdigit():
build = int(build)
patch = ""
else:
patch = build[1:]
build = int(build[0])
if patch and patch[0].isdigit():
return False
return major, minor, build, patch

@classmethod
def build_value(cls, string):
return cls.parse(string)

def __str__(self):
return self.normalized_string


@attr.s(frozen=True, order=False, eq=False, hash=True)
class OpensslVersion(Version):

"""
Internally tracks two types of openssl versions
- Legacy versions: Implemented in LegacyOpensslVersion
- New versions: Semver
For example::
>>> old = OpensslVersion("1.1.0f")
>>> new = OpensslVersion("3.0.1")
>>> assert old == OpensslVersion(string="1.1.0f")
>>> assert new == OpensslVersion(string="3.0.1")
>>> assert old.value == LegacyOpensslVersion(string="1.1.0f")
>>> assert new.value == SemverVersion(string="3.0.1")
>>> OpensslVersion("1.2.4fg")
Traceback (most recent call last):
...
univers.versions.InvalidVersion: '1.2.4fg' is not a valid <class 'univers.versions.OpensslVersion'>
"""

@classmethod
def is_valid(cls, string):
return cls.is_valid_new(string) or cls.is_valid_legacy(string)

@classmethod
def build_value(cls, string):
"""
Return a wrapped version "value" object depending on
whether version is legacy or semver.
"""
if cls.is_valid_legacy(string):
return LegacyOpensslVersion(string)
if cls.is_valid_new(string):
return SemverVersion(string)

@classmethod
def is_valid_new(cls, string):
"""
Checks the validity of new Openssl Version.
For example::
>>> OpensslVersion.is_valid_new("1.0.1f")
False
>>> OpensslVersion.is_valid_new("3.0.0")
True
>>> OpensslVersion.is_valid_new("3.0.2")
True
"""
if SemverVersion.is_valid(string):
sem = semantic_version.Version.coerce(string)
return sem.major >= 3

@classmethod
def is_valid_legacy(cls, string):
return LegacyOpensslVersion.is_valid(string)

def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if not isinstance(other.value, self.value.__class__):
return NotImplemented
return self.value.__eq__(other.value)

def __lt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if isinstance(other.value, self.value.__class__):
return self.value.__lt__(other.value)
# By construction legacy version is always behind Semver
return isinstance(self.value, LegacyOpensslVersion)

def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if isinstance(other.value, self.value.__class__):
return self.value.__gt__(other.value)
# By construction semver version is always ahead of legacy
return isinstance(self.value, SemverVersion)

def __le__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if isinstance(other.value, self.value.__class__):
return self.value.__le__(other.value)
# version value are of diff type, then legacy one is always behind semver
return isinstance(self.value, LegacyOpensslVersion)

def __ge__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if isinstance(other.value, self.value.__class__):
return self.value.__ge__(other.value)
# version value are of diff type, then semver one is always ahead of legacy
return isinstance(self.value, SemverVersion)
32 changes: 32 additions & 0 deletions tests/test_version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
from univers.version_range import VersionRange
from univers.version_range import RANGE_CLASS_BY_SCHEMES
from univers.version_range import NpmVersionRange
from univers.version_range import OpensslVersionRange
from univers.versions import PypiVersion
from univers.versions import RubygemsVersion
from univers.versions import SemverVersion
from univers.versions import OpensslVersion


class TestVersionRange(TestCase):
Expand Down Expand Up @@ -233,10 +235,40 @@ def test_NpmVersionRange_from_native_with_approximately_equal_to_operator(self):
version_range = NpmVersionRange.from_native(npm_range)
assert version_range == expected

def test_OpensslVersionRange_from_native_single_legacy(self):
openssl_range = "0.9.8j"
expected = OpensslVersionRange(
constraints=(
VersionConstraint(comparator="=", version=OpensslVersion(string="0.9.8j")),
)
)
version_range = OpensslVersionRange.from_native(openssl_range)
assert version_range == expected

def test_OpensslVersionRange_from_native_single_new_semver(self):
openssl_range = "3.0.1"
expected = OpensslVersionRange(
constraints=(VersionConstraint(comparator="=", version=OpensslVersion(string="3.0.1")),)
)
version_range = OpensslVersionRange.from_native(openssl_range)
assert version_range == expected

def test_OpensslVersionRange_from_native_mixed(self):
openssl_range = "3.0.0, 1.0.1b"
expected = OpensslVersionRange(
constraints=(
VersionConstraint(comparator="=", version=OpensslVersion(string="1.0.1b")),
VersionConstraint(comparator="=", version=OpensslVersion(string="3.0.0")),
)
)
version_range = OpensslVersionRange.from_native(openssl_range)
assert version_range == expected


VERSION_RANGE_TESTS_BY_SCHEME = {
"nginx": ["0.8.40+", "0.7.52-0.8.39", "0.9.10", "1.5.0+, 1.4.1+"],
"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"],
"openssl": ["1.1.1ak", "1.1.0", "3.0.2", "3.0.1, 0.9.7a", "1.0.2ck, 3.1.2"],
}


Expand Down

0 comments on commit 6f03cf6

Please sign in to comment.