mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -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_from?', autofill=False)
|
||||||
option: DateTime('issuedon_to?', autofill=False)
|
option: DateTime('issuedon_to?', autofill=False)
|
||||||
option: DNParam('issuer?', autofill=False)
|
option: DNParam('issuer?', autofill=False)
|
||||||
option: Int('max_serial_number?', autofill=False)
|
option: SerialNumber('max_serial_number?', autofill=False)
|
||||||
option: Int('min_serial_number?', autofill=False)
|
option: SerialNumber('min_serial_number?', autofill=False)
|
||||||
option: Str('no_host*', cli_name='no_hosts')
|
option: Str('no_host*', cli_name='no_hosts')
|
||||||
option: Flag('no_members', autofill=True, default=True)
|
option: Flag('no_members', autofill=True, default=True)
|
||||||
option: Principal('no_service*', cli_name='no_services')
|
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'>])
|
output: Output('truncated', type=[<type 'bool'>])
|
||||||
command: cert_remove_hold/1
|
command: cert_remove_hold/1
|
||||||
args: 1,2,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('cacn?', autofill=True, cli_name='ca', default=u'ipa')
|
||||||
option: Str('version?')
|
option: Str('version?')
|
||||||
output: Output('result')
|
output: Output('result')
|
||||||
@ -811,14 +811,14 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
|||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
command: cert_revoke/1
|
command: cert_revoke/1
|
||||||
args: 1,3,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: Str('cacn?', autofill=True, cli_name='ca', default=u'ipa')
|
||||||
option: Int('revocation_reason', autofill=True, default=0)
|
option: Int('revocation_reason', autofill=True, default=0)
|
||||||
option: Str('version?')
|
option: Str('version?')
|
||||||
output: Output('result')
|
output: Output('result')
|
||||||
command: cert_show/1
|
command: cert_show/1
|
||||||
args: 1,7,3
|
args: 1,7,3
|
||||||
arg: Int('serial_number')
|
arg: SerialNumber('serial_number')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
option: Str('cacn?', autofill=True, cli_name='ca', default=u'ipa')
|
option: Str('cacn?', autofill=True, cli_name='ca', default=u'ipa')
|
||||||
option: Flag('chain', autofill=True, default=False)
|
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 Command, LocalOrRemote, Updater
|
||||||
from ipalib.frontend import Object, Method
|
from ipalib.frontend import Object, Method
|
||||||
from ipalib.crud import Create, Retrieve, Update, Delete, Search
|
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,
|
from ipalib.parameters import (BytesEnum, StrEnum, IntEnum, AccessTime, File,
|
||||||
DateTime, DNSNameParam)
|
DateTime, DNSNameParam)
|
||||||
from ipalib.errors import SkipPluginModule
|
from ipalib.errors import SkipPluginModule
|
||||||
|
@ -2254,3 +2254,47 @@ def create_signature(command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return signature
|
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
|
from dns import resolver, reversename
|
||||||
import six
|
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 api
|
||||||
from ipalib import errors, messages
|
from ipalib import errors, messages
|
||||||
from ipalib import x509
|
from ipalib import x509
|
||||||
@ -446,7 +446,7 @@ class BaseCertObject(Object):
|
|||||||
label=_('Fingerprint (SHA256)'),
|
label=_('Fingerprint (SHA256)'),
|
||||||
flags={'no_create', 'no_update', 'no_search'},
|
flags={'no_create', 'no_update', 'no_search'},
|
||||||
),
|
),
|
||||||
Int(
|
SerialNumber(
|
||||||
'serial_number',
|
'serial_number',
|
||||||
label=_('Serial number'),
|
label=_('Serial number'),
|
||||||
doc=_('Serial number in decimal or if prefixed with 0x in hexadecimal'),
|
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
|
# Dogtag lightweight CAs have shared serial number domain, so
|
||||||
# we don't tell Dogtag the issuer (but we check the cert after).
|
# 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(
|
cert = x509.load_der_x509_certificate(
|
||||||
base64.b64decode(result['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.
|
# Make sure that the cert specified by issuer+serial exists.
|
||||||
# Will raise NotFound if it does not.
|
# 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:
|
try:
|
||||||
self.check_access()
|
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
|
# we don't tell Dogtag the issuer (but we already checked that
|
||||||
# the given serial was issued by the named ca).
|
# the given serial was issued by the named ca).
|
||||||
result=self.Backend.ra.revoke_certificate(
|
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
|
# we don't tell Dogtag the issuer (but we already checked that
|
||||||
# the given serial was issued by the named ca).
|
# the given serial was issued by the named ca).
|
||||||
result=self.Backend.ra.take_certificate_off_hold(
|
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'),
|
doc=_('Match cn attribute in subject'),
|
||||||
autofill=False,
|
autofill=False,
|
||||||
),
|
),
|
||||||
Int('min_serial_number?',
|
SerialNumber('min_serial_number?',
|
||||||
doc=_("minimum serial number"),
|
doc=_("minimum serial number"),
|
||||||
autofill=False,
|
autofill=False,
|
||||||
minvalue=0,
|
|
||||||
maxvalue=2147483647,
|
|
||||||
),
|
),
|
||||||
Int('max_serial_number?',
|
SerialNumber('max_serial_number?',
|
||||||
doc=_("maximum serial number"),
|
doc=_("maximum serial number"),
|
||||||
autofill=False,
|
autofill=False,
|
||||||
minvalue=0,
|
|
||||||
maxvalue=2147483647,
|
|
||||||
),
|
),
|
||||||
Flag('exactly?',
|
Flag('exactly?',
|
||||||
doc=_('match the common name exactly'),
|
doc=_('match the common name exactly'),
|
||||||
@ -1896,7 +1894,9 @@ class cert_find(Search, CertMethod):
|
|||||||
ca_obj = ca_objs[cacn] = (
|
ca_obj = ca_objs[cacn] = (
|
||||||
self.api.Command.ca_show(cacn, all=True)['result'])
|
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:
|
if not raw:
|
||||||
obj['certificate'] = (
|
obj['certificate'] = (
|
||||||
obj['certificate'].replace('\r\n', ''))
|
obj['certificate'].replace('\r\n', ''))
|
||||||
|
@ -1858,3 +1858,44 @@ class test_DNParam(ClassChecker):
|
|||||||
for value in good:
|
for value in good:
|
||||||
assert mthd(value) == tuple(DN(oneval) for oneval in value)
|
assert mthd(value) == tuple(DN(oneval) for oneval in value)
|
||||||
assert o.convert(None) is None
|
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