mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-01-26 16:16:31 -06:00
Add a new parameter type, SerialNumber, as a subclass of Str
Transmitting a big integer like a random serial number over either xmlrpc or JSON is problematic because they only support 32-bit integers at best. A random serial number can be as big as 128 bits (theoretically 160 but dogtag limits it). Treat as a string instead. Internally the value can be treated as an Integer to conversions to/from hex as needed but for transmission purposes handle it as a string. Fixes: https://pagure.io/freeipa/issue/2016 Signed-off-by: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Florence Blanc-Renaud <flo@redhat.com> Reviewed-By: Francisco Trivino <ftrivino@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
parent
d3481449ee
commit
83be923ac5
10
API.txt
10
API.txt
@ -762,8 +762,8 @@ option: Str('host*', cli_name='hosts')
|
||||
option: DateTime('issuedon_from?', autofill=False)
|
||||
option: DateTime('issuedon_to?', autofill=False)
|
||||
option: DNParam('issuer?', autofill=False)
|
||||
option: Int('max_serial_number?', autofill=False)
|
||||
option: Int('min_serial_number?', autofill=False)
|
||||
option: SerialNumber('max_serial_number?', autofill=False)
|
||||
option: SerialNumber('min_serial_number?', autofill=False)
|
||||
option: Str('no_host*', cli_name='no_hosts')
|
||||
option: Flag('no_members', autofill=True, default=True)
|
||||
option: Principal('no_service*', cli_name='no_services')
|
||||
@ -790,7 +790,7 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: Output('truncated', type=[<type 'bool'>])
|
||||
command: cert_remove_hold/1
|
||||
args: 1,2,1
|
||||
arg: Int('serial_number')
|
||||
arg: SerialNumber('serial_number')
|
||||
option: Str('cacn?', autofill=True, cli_name='ca', default=u'ipa')
|
||||
option: Str('version?')
|
||||
output: Output('result')
|
||||
@ -811,14 +811,14 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: PrimaryKey('value')
|
||||
command: cert_revoke/1
|
||||
args: 1,3,1
|
||||
arg: Int('serial_number')
|
||||
arg: SerialNumber('serial_number')
|
||||
option: Str('cacn?', autofill=True, cli_name='ca', default=u'ipa')
|
||||
option: Int('revocation_reason', autofill=True, default=0)
|
||||
option: Str('version?')
|
||||
output: Output('result')
|
||||
command: cert_show/1
|
||||
args: 1,7,3
|
||||
arg: Int('serial_number')
|
||||
arg: SerialNumber('serial_number')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
option: Str('cacn?', autofill=True, cli_name='ca', default=u'ipa')
|
||||
option: Flag('chain', autofill=True, default=False)
|
||||
|
@ -921,7 +921,10 @@ from ipalib.backend import Backend
|
||||
from ipalib.frontend import Command, LocalOrRemote, Updater
|
||||
from ipalib.frontend import Object, Method
|
||||
from ipalib.crud import Create, Retrieve, Update, Delete, Search
|
||||
from ipalib.parameters import DefaultFrom, Bool, Flag, Int, Decimal, Bytes, Str, IA5Str, Password, DNParam
|
||||
from ipalib.parameters import (
|
||||
DefaultFrom, Bool, Flag, Int, Decimal, Bytes, Str, IA5Str,
|
||||
Password, DNParam, SerialNumber
|
||||
)
|
||||
from ipalib.parameters import (BytesEnum, StrEnum, IntEnum, AccessTime, File,
|
||||
DateTime, DNSNameParam)
|
||||
from ipalib.errors import SkipPluginModule
|
||||
|
@ -2254,3 +2254,47 @@ def create_signature(command):
|
||||
)
|
||||
|
||||
return signature
|
||||
|
||||
|
||||
class SerialNumber(Str):
|
||||
"""Certificate serial number parameter type
|
||||
"""
|
||||
|
||||
type = str
|
||||
allowed_types = (str,)
|
||||
|
||||
# FIXME: currently unused, perhaps drop it
|
||||
MAX_VALUE = 340282366920938463463374607431768211456 # 2^128
|
||||
|
||||
kwargs = Param.kwargs + (
|
||||
('minlength', int, 1),
|
||||
('maxlength', int, 40), # Up to 128-bit values
|
||||
('length', int, None),
|
||||
)
|
||||
|
||||
def _validate_scalar(self, value, index=None):
|
||||
super(SerialNumber, self)._validate_scalar(value)
|
||||
if value.startswith('-'):
|
||||
raise ValidationError(
|
||||
name=self.name, error=_('must be at least 0')
|
||||
)
|
||||
if not value.isdigit():
|
||||
if value.lower().startswith('0x'):
|
||||
try:
|
||||
int(value[2:], 16)
|
||||
except ValueError:
|
||||
raise ValidationError(
|
||||
name=self.name, error=_(
|
||||
_('invalid valid hex'),
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise ValidationError(
|
||||
name=self.name, error=_(
|
||||
_('must be an integer'),
|
||||
)
|
||||
)
|
||||
if value == '0':
|
||||
raise ValidationError(
|
||||
name=self.name, error=_('invalid serial number 0')
|
||||
)
|
||||
|
@ -31,7 +31,7 @@ from cryptography.hazmat.primitives import hashes, serialization
|
||||
from dns import resolver, reversename
|
||||
import six
|
||||
|
||||
from ipalib import Command, Str, Int, Flag, StrEnum
|
||||
from ipalib import Command, Str, Int, Flag, StrEnum, SerialNumber
|
||||
from ipalib import api
|
||||
from ipalib import errors, messages
|
||||
from ipalib import x509
|
||||
@ -446,7 +446,7 @@ class BaseCertObject(Object):
|
||||
label=_('Fingerprint (SHA256)'),
|
||||
flags={'no_create', 'no_update', 'no_search'},
|
||||
),
|
||||
Int(
|
||||
SerialNumber(
|
||||
'serial_number',
|
||||
label=_('Serial number'),
|
||||
doc=_('Serial number in decimal or if prefixed with 0x in hexadecimal'),
|
||||
@ -1370,7 +1370,7 @@ class cert_show(Retrieve, CertMethod, VirtualCommand):
|
||||
# Dogtag lightweight CAs have shared serial number domain, so
|
||||
# we don't tell Dogtag the issuer (but we check the cert after).
|
||||
#
|
||||
result = self.Backend.ra.get_certificate(str(serial_number))
|
||||
result = self.Backend.ra.get_certificate(serial_number)
|
||||
cert = x509.load_der_x509_certificate(
|
||||
base64.b64decode(result['certificate']))
|
||||
|
||||
@ -1443,7 +1443,7 @@ class cert_revoke(PKQuery, CertMethod, VirtualCommand):
|
||||
|
||||
# Make sure that the cert specified by issuer+serial exists.
|
||||
# Will raise NotFound if it does not.
|
||||
resp = api.Command.cert_show(unicode(serial_number), cacn=kw['cacn'])
|
||||
resp = api.Command.cert_show(serial_number, cacn=kw['cacn'])
|
||||
|
||||
try:
|
||||
self.check_access()
|
||||
@ -1465,7 +1465,8 @@ class cert_revoke(PKQuery, CertMethod, VirtualCommand):
|
||||
# we don't tell Dogtag the issuer (but we already checked that
|
||||
# the given serial was issued by the named ca).
|
||||
result=self.Backend.ra.revoke_certificate(
|
||||
str(serial_number), revocation_reason=revocation_reason)
|
||||
serial_number,
|
||||
revocation_reason=revocation_reason)
|
||||
)
|
||||
|
||||
|
||||
@ -1489,7 +1490,8 @@ class cert_remove_hold(PKQuery, CertMethod, VirtualCommand):
|
||||
# we don't tell Dogtag the issuer (but we already checked that
|
||||
# the given serial was issued by the named ca).
|
||||
result=self.Backend.ra.take_certificate_off_hold(
|
||||
str(serial_number))
|
||||
serial_number
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -1503,17 +1505,13 @@ class cert_find(Search, CertMethod):
|
||||
doc=_('Match cn attribute in subject'),
|
||||
autofill=False,
|
||||
),
|
||||
Int('min_serial_number?',
|
||||
SerialNumber('min_serial_number?',
|
||||
doc=_("minimum serial number"),
|
||||
autofill=False,
|
||||
minvalue=0,
|
||||
maxvalue=2147483647,
|
||||
),
|
||||
Int('max_serial_number?',
|
||||
SerialNumber('max_serial_number?',
|
||||
doc=_("maximum serial number"),
|
||||
autofill=False,
|
||||
minvalue=0,
|
||||
maxvalue=2147483647,
|
||||
),
|
||||
Flag('exactly?',
|
||||
doc=_('match the common name exactly'),
|
||||
@ -1896,7 +1894,9 @@ class cert_find(Search, CertMethod):
|
||||
ca_obj = ca_objs[cacn] = (
|
||||
self.api.Command.ca_show(cacn, all=True)['result'])
|
||||
|
||||
obj.update(ra.get_certificate(str(serial_number)))
|
||||
obj.update(
|
||||
ra.get_certificate(serial_number)
|
||||
)
|
||||
if not raw:
|
||||
obj['certificate'] = (
|
||||
obj['certificate'].replace('\r\n', ''))
|
||||
|
@ -1858,3 +1858,44 @@ class test_DNParam(ClassChecker):
|
||||
for value in good:
|
||||
assert mthd(value) == tuple(DN(oneval) for oneval in value)
|
||||
assert o.convert(None) is None
|
||||
|
||||
|
||||
class test_SerialNumber(ClassChecker):
|
||||
"""
|
||||
Test the `ipalib.parameters.SerialNumber` class.
|
||||
"""
|
||||
_cls = parameters.SerialNumber
|
||||
|
||||
def test_init(self):
|
||||
"""
|
||||
Test the `ipalib.parameters.SerialNumber.__init__` method.
|
||||
"""
|
||||
o = self.cls('my_serial')
|
||||
assert o.type is str
|
||||
assert o.length is None
|
||||
|
||||
def test_validate_scalar(self):
|
||||
"""
|
||||
Test the `ipalib.parameters.SerialNumber._convert_scalar` method.
|
||||
"""
|
||||
o = self.cls('my_serial')
|
||||
mthd = o._validate_scalar
|
||||
for value in ('1234', '0xabcd', '0xABCD'):
|
||||
assert mthd(value) is None
|
||||
bad = ['Hello', '123A']
|
||||
for value in bad:
|
||||
e = raises(errors.ValidationError, mthd, value)
|
||||
assert e.name == 'my_serial'
|
||||
assert_equal(e.error, 'must be an integer')
|
||||
bad = ['-1234', '-0xAFF']
|
||||
for value in bad:
|
||||
e = raises(errors.ValidationError, mthd, value)
|
||||
assert e.name == 'my_serial'
|
||||
assert_equal(e.error, 'must be at least 0')
|
||||
bad = ['0xGAH', '0x',]
|
||||
for value in bad:
|
||||
e = raises(errors.ValidationError, mthd, value)
|
||||
assert e.name == 'my_serial'
|
||||
assert_equal(
|
||||
e.error, 'invalid valid hex'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user