Add support for Random Serial Numbers v3

Dogtag has implemented a new random serial number scheme
they are calling RSNv3.

https://github.com/dogtagpki/pki/wiki/Random-Certificate-Serial-Numbers-v3

Given the known issues reported this will be supported in IPA for
new installations only.

There is no mixing of random servers and non-random servers
allowed.

Instructions for installing a CA:
https://github.com/dogtagpki/pki/blob/master/docs/installation/ca/Installing-CA-with-Random-Serial-Numbers-v3.adoc

Instructions for installing a KRA:
https://github.com/dogtagpki/pki/blob/master/docs/installation/kra/Installig-KRA-with-Random-Serial-Numbers-v3.adoc

The version of random serial numbers is stored within the CA entry
of the server. It is stored as a version to allow for future upgrades.

If a CA has RSN enabled then any KRA installed will also have it
enabled for its identifiers.

A new attribute, ipaCaRandomSerialNumberVersion, is added to the IPA CA
entry to track the version number in case PKI has future major
revisions. This can also be used to determine if RSN is enabled or not.

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:
Rob Crittenden
2022-05-16 17:29:26 +00:00
committed by Florence Blanc-Renaud
parent 83be923ac5
commit beaa0562dc
13 changed files with 164 additions and 17 deletions

View File

@@ -29,7 +29,7 @@ aci: (targetfilter = "(objectclass=ipaca)")(version 3.0;acl "permission:System:
dn: cn=cas,cn=ca,dc=ipa,dc=example
aci: (targetattr = "cn || description")(targetfilter = "(objectclass=ipaca)")(version 3.0;acl "permission:System: Modify CA";allow (write) groupdn = "ldap:///cn=System: Modify CA,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=cas,cn=ca,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || description || entryusn || ipacaid || ipacaissuerdn || ipacasubjectdn || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaca)")(version 3.0;acl "permission:System: Read CAs";allow (compare,read,search) userdn = "ldap:///all";)
aci: (targetattr = "cn || createtimestamp || description || entryusn || ipacaid || ipacaissuerdn || ipacarandomserialnumberversion || ipacasubjectdn || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaca)")(version 3.0;acl "permission:System: Read CAs";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=caacls,cn=ca,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=ipacaacl)")(version 3.0;acl "permission:System: Add CA ACL";allow (add) groupdn = "ldap:///cn=System: Add CA ACL,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=caacls,cn=ca,dc=ipa,dc=example

View File

@@ -493,13 +493,14 @@ output: Output('result', type=[<type 'bool'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: ca_find/1
args: 1,11,4
args: 1,12,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('cn?', autofill=False, cli_name='name')
option: Str('description?', autofill=False, cli_name='desc')
option: Str('ipacaid?', autofill=False, cli_name='id')
option: DNParam('ipacaissuerdn?', autofill=False, cli_name='issuer')
option: Int('ipacarandomserialnumberversion?', autofill=False, cli_name='randomserialnumberversion')
option: DNParam('ipacasubjectdn?', autofill=False, cli_name='subject')
option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
@@ -831,7 +832,7 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: cert_status/1
args: 1,4,3
arg: Int('request_id')
arg: Str('request_id')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('cacn?', autofill=True, cli_name='ca', default=u'ipa')
option: Flag('raw', autofill=True, cli_name='raw', default=False)

View File

@@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000)
# #
########################################################
define(IPA_API_VERSION_MAJOR, 2)
# Last change: add graceperiodlimit
define(IPA_API_VERSION_MINOR, 248)
# Last change: add Random Serial Numbers v3
define(IPA_API_VERSION_MINOR, 249)
########################################################
# Following values are auto-generated from values above

View File

@@ -7,6 +7,7 @@ attributeTypes: (2.16.840.1.113730.3.8.21.1.5 NAME 'ipaCertProfileCategory' DESC
attributeTypes: (2.16.840.1.113730.3.8.21.1.6 NAME 'ipaCaId' DESC 'Dogtag Authority ID' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.4 Lightweight CAs' )
attributeTypes: (2.16.840.1.113730.3.8.21.1.7 NAME 'ipaCaIssuerDN' DESC 'Issuer DN' SUP distinguishedName X-ORIGIN 'IPA v4.4 Lightweight CAs' )
attributeTypes: (2.16.840.1.113730.3.8.21.1.8 NAME 'ipaCaSubjectDN' DESC 'Subject DN' SUP distinguishedName X-ORIGIN 'IPA v4.4 Lightweight CAs' )
attributeTypes: (2.16.840.1.113730.3.8.21.1.9 NAME 'ipaCaRandomSerialNumberVersion' DESC 'Random Serial Number Version' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9 RSNv3' )
objectClasses: (2.16.840.1.113730.3.8.21.2.1 NAME 'ipaCertProfile' SUP top STRUCTURAL MUST ( cn $ description $ ipaCertProfileStoreIssued ) X-ORIGIN 'IPA v4.2' )
objectClasses: (2.16.840.1.113730.3.8.21.2.2 NAME 'ipaCaAcl' SUP ipaAssociation STRUCTURAL MUST cn MAY ( ipaCaCategory $ ipaCertProfileCategory $ userCategory $ hostCategory $ serviceCategory $ ipaMemberCa $ ipaMemberCertProfile $ memberService ) X-ORIGIN 'IPA v4.2' )
objectClasses: (2.16.840.1.113730.3.8.21.2.3 NAME 'ipaCa' SUP top STRUCTURAL MUST ( cn $ ipaCaId $ ipaCaSubjectDN $ ipaCaIssuerDN ) MAY description X-ORIGIN 'IPA v4.4 Lightweight CAs' )
objectClasses: (2.16.840.1.113730.3.8.21.2.3 NAME 'ipaCa' SUP top STRUCTURAL MUST ( cn $ ipaCaId $ ipaCaSubjectDN $ ipaCaIssuerDN ) MAY ( description $ ipaCaRandomSerialNumberVersion ) X-ORIGIN 'IPA v4.4 Lightweight CAs' )

View File

@@ -85,7 +85,6 @@ pki_subsystem_key_type=%(ipa_key_type)s
pki_subsystem_token=%(pki_token_name)s
[CA]
pki_random_serial_numbers_enable=False
## caSigningCert cert-pki-ca
pki_ca_signing_key_algorithm=%(ipa_ca_key_algorithm)s

View File

@@ -106,6 +106,10 @@ def parse_options():
default=None,
help="Path to ini file with config overrides.")
parser.add_option("--random-serial-numbers", dest="random_serial_numbers",
default=False, help="Enable random serial numbers",
action="store_true")
options, args = parser.parse_args()
safe_options = parser.get_safe_opts(options)
@@ -226,6 +230,10 @@ def install_master(safe_options, options):
options.ca_subject = str(
installutils.default_ca_subject_dn(options.subject_base))
try:
ca.random_serial_numbers_validator(options.random_serial_numbers)
except ValueError as e:
sys.exit(str(e))
try:
ca.subject_validator(ca.VALID_SUBJECT_BASE_ATTRS, options.subject_base)
except ValueError as e:
@@ -270,6 +278,11 @@ def install(safe_options, options):
if ca_host is None:
install_master(safe_options, options)
else:
if options.random_serial_numbers:
if ca.lookup_random_serial_number_version(api) == 0:
sys.exit(
"\nRandom serial numbers cannot be enabled in an "
"existing CA installation.\n")
install_replica(safe_options, options)

View File

@@ -82,6 +82,9 @@ Signing algorithm of the IPA CA certificate. Possible values are SHA1withRSA, SH
\fB\-\-no\-host\-dns\fR
Do not use DNS for hostname lookup during installation
.TP
\fB\-\-random\-serial\-numbers\fR
Enable Random Serial Numbers. Random serial numbers cannot be used in a mixed environment. Either all CA's have it enabled or none do.
.TP
\fB\-\-skip\-conncheck\fR
Skip connection check to remote master
.TP

View File

@@ -122,6 +122,9 @@ If no template is specified, the template name "SubCA" is used.
\fB\-\-external\-cert\-file\fR=\fIFILE\fR
File containing the IPA CA certificate and the external CA certificate chain. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times.
.TP
\fB\-\-random\-serial\-numbers\fR
Enable Random Serial Numbers. Random serial numbers cannot be used in a mixed environment. Either all CA's have it enabled or none do.
.TP
\fB\-\-no\-pkinit\fR
Disables pkinit setup steps.
.TP

View File

@@ -11,6 +11,7 @@ from __future__ import print_function, absolute_import
import enum
import logging
import os.path
import pki.util
import six
@@ -69,6 +70,26 @@ def subject_validator(valid_attrs, value):
raise ValueError("invalid DN: %s" % e)
def random_serial_numbers_version(enabled):
"""Return True if PKI supports RSNv3
The caller is responsible for raising the exception.
"""
if not enabled:
return None, None
pki_version = pki.util.Version(pki.specification_version())
return pki_version >= pki.util.Version("11.2.0"), pki_version
def random_serial_numbers_validator(enabled):
val, pki_version = random_serial_numbers_version(enabled)
if val is False:
raise ValueError(
"Random Serial Numbers are not supported in PKI version %s"
% pki_version
)
def lookup_ca_subject(api, subject_base):
dn = DN(('cn', IPA_CA_CN), api.env.container_ca, api.env.basedn)
try:
@@ -88,6 +109,38 @@ def lookup_ca_subject(api, subject_base):
return str(ca_subject)
def lookup_random_serial_number_version(api):
"""
Retrieve the random serial number version number from the
remote server.
If the value is > 0 then RSN was enabled. Return the raw
value for future-proofing in case version-specific decisions
need to be made.
Returns 0 if RSN is not enabled or otherwise not available.
"""
dn = DN(('cn', IPA_CA_CN), api.env.container_ca, api.env.basedn)
version = 0
try:
# we do not use api.Command.ca_show because it attempts to
# talk to the CA (to read certificate / chain), but the RA
# backend may be unavailable (ipa-replica-install) or unusable
# due to RA Agent cert not yet created (ipa-ca-install).
entry = api.Backend.ldap2.get_entry(dn)
# If the attribute doesn't exist then the remote didn't
# enable RSN.
if 'ipacarandomserialnumberversion' in entry:
version = int(entry['ipacarandomserialnumberversion'][0])
except (errors.NotFound, KeyError):
# if the entry doesn't exist then the remote doesn't support
# RSN so there is nothing to do.
pass
return version
def set_subject_base_in_config(subject_base):
entry_attrs = api.Backend.ldap2.get_ipa_config()
entry_attrs['ipacertificatesubjectbase'] = [str(subject_base)]
@@ -156,6 +209,7 @@ def install_check(standalone, replica_config, options):
if replica_config is None:
options._subject_base = options.subject_base
options._ca_subject = options.ca_subject
options._random_serial_numbers = options.random_serial_numbers
else:
# during replica install, this gets invoked before local DS is
# available, so use the remote api.
@@ -165,6 +219,18 @@ def install_check(standalone, replica_config, options):
options._subject_base = str(replica_config.subject_base)
options._ca_subject = lookup_ca_subject(_api, options._subject_base)
options._random_serial_numbers = (
lookup_random_serial_number_version(_api) > 0
)
if options._random_serial_numbers and replica_config.setup_ca:
try:
random_serial_numbers_validator(
options._random_serial_numbers
)
except ValueError as e:
raise ScriptError(str(e))
if replica_config is not None and not replica_config.setup_ca:
return
@@ -353,6 +419,7 @@ def install_step_0(standalone, replica_config, options, custodia):
promote=promote,
use_ldaps=use_ldaps,
pki_config_override=options.pki_config_override,
random_serial_numbers=options._random_serial_numbers,
)
@@ -545,3 +612,13 @@ class CAInstallInterface(dogtag.DogtagInstallInterface,
)
skip_schema_check = enroll_only(skip_schema_check)
skip_schema_check = replica_install_only(skip_schema_check)
random_serial_numbers = knob(
None,
description="Enable random serial numbers",
)
random_serial_numbers = master_install_only(random_serial_numbers)
@random_serial_numbers.validator
def random_serial_numbers(self, value):
random_serial_numbers_validator(value)

View File

@@ -345,7 +345,8 @@ class CAInstance(DogtagInstance):
ca_type=None, external_ca_profile=None,
ra_p12=None, ra_only=False,
promote=False, use_ldaps=False,
pki_config_override=None):
pki_config_override=None,
random_serial_numbers=False):
"""Create a CA instance.
To create a clone, pass in pkcs12_info.
@@ -385,6 +386,7 @@ class CAInstance(DogtagInstance):
else:
self.ca_type = x509.ExternalCAType.GENERIC.value
self.external_ca_profile = external_ca_profile
self.random_serial_numbers = random_serial_numbers
self.no_db_setup = promote
self.use_ldaps = use_ldaps
@@ -481,6 +483,9 @@ class CAInstance(DogtagInstance):
migrate_profiles_to_ldap)
self.step("adding default CA ACL", ensure_default_caacl)
self.step("adding 'ipa' CA entry", ensure_ipa_authority_entry)
if not self.clone:
self.step("Recording random serial number state",
self.__store_random_serial_number_state)
else:
# Re-import profiles in the promote case to pick up any
# that will only be triggered by an upgrade.
@@ -520,6 +525,11 @@ class CAInstance(DogtagInstance):
if self.ca_signing_algorithm is not None:
cfg['ipa_ca_signing_algorithm'] = self.ca_signing_algorithm
cfg['pki_random_serial_numbers_enable'] = self.random_serial_numbers
if self.random_serial_numbers:
cfg['pki_request_id_generator'] = 'random'
cfg['pki_cert_id_generator'] = 'random'
if not (os.path.isdir(paths.PKI_TOMCAT_ALIAS_DIR) and
os.path.isfile(paths.PKI_TOMCAT_PASSWORD_CONF)):
# generate pin which we know can be used for FIPS NSS database
@@ -557,6 +567,17 @@ class CAInstance(DogtagInstance):
shutil.copy(cafile, paths.TMP_CA_P12)
self.service_user.chown(paths.TMP_CA_P12)
if self.random_serial_numbers:
cfg.update(
pki_random_serial_numbers_enable=True,
pki_request_id_generator="random",
pki_cert_id_generator="random",
)
else:
cfg.update(
pki_random_serial_numbers_enable=False,
)
self._configure_clone(
cfg,
security_domain_hostname=self.master_host,
@@ -1552,6 +1573,24 @@ class CAInstance(DogtagInstance):
return True
def __store_random_serial_number_state(self):
"""
Save the Random Serial Number (RSN) version.
This is intended to add flexibility in case RSN bumps
another version in dogtag. For now we only support v3
or no randomization (0).
"""
if self.random_serial_numbers:
value = 3
else:
value = 0
dn = DN(('cn', ipalib.constants.IPA_CA_CN), api.env.container_ca,
api.env.basedn)
entry_attrs = api.Backend.ldap2.get_entry(dn)
entry_attrs['ipaCaRandomSerialNumberVersion'] = value
api.Backend.ldap2.update_entry(entry_attrs)
def __update_entry_from_cert(make_filter, make_entry, cert):
"""

View File

@@ -35,6 +35,7 @@ from ipapython.dn import DN
from ipaserver.install import cainstance
from ipaserver.install import installutils
from ipaserver.install.dogtaginstance import DogtagInstance
from ipaserver.install.ca import lookup_random_serial_number_version
logger = logging.getLogger(__name__)
@@ -165,6 +166,9 @@ class KRAInstance(DogtagInstance):
pki_import_admin_cert=False,
pki_client_admin_cert_p12=admin_p12_file,
)
if lookup_random_serial_number_version(api) > 0:
cfg['pki_key_id_generator'] = 'random'
cfg['pki_request_id_generator'] = 'random'
if not (os.path.isdir(paths.PKI_TOMCAT_ALIAS_DIR) and
os.path.isfile(paths.PKI_TOMCAT_PASSWORD_CONF)):

View File

@@ -6,7 +6,8 @@ import base64
import six
from ipalib import api, errors, messages, output, Bytes, DNParam, Flag, Str
from ipalib import api, errors, messages, output
from ipalib import Bytes, DNParam, Flag, Str, Int
from ipalib.constants import IPA_CA_CN
from ipalib.plugable import Registry
from ipapython.dn import ATTR_NAME_BY_OID
@@ -71,6 +72,7 @@ class ca(LDAPObject):
permission_filter_objectclasses = ['ipaca']
default_attributes = [
'cn', 'description', 'ipacaid', 'ipacaissuerdn', 'ipacasubjectdn',
'ipacarandomserialnumberversion',
]
rdn_attribute = 'cn'
allow_rename = True
@@ -119,6 +121,13 @@ class ca(LDAPObject):
doc=_("X.509 certificate chain"),
flags={'no_create', 'no_update', 'no_search'},
),
Int(
'ipacarandomserialnumberversion',
cli_name='randomserialnumberversion',
label=_('RSN Version'),
doc=_('Random Serial Number Version'),
flags={'no_create', 'no_update'},
),
)
permission_filter_objectclasses = ['ipaca']
@@ -133,6 +142,7 @@ class ca(LDAPObject):
'ipacaid',
'ipacaissuerdn',
'ipacasubjectdn',
'ipacarandomserialnumberversion',
'objectclass',
},
},

View File

@@ -483,7 +483,8 @@ class BaseCertObject(Object):
base64.b64decode(obj['certificate']))
obj['subject'] = DN(cert.subject)
obj['issuer'] = DN(cert.issuer)
obj['serial_number'] = cert.serial_number
obj['serial_number'] = str(cert.serial_number)
obj['serial_number_hex'] = '0x%X' % cert.serial_number
obj['valid_not_before'] = x509.format_datetime(
cert.not_valid_before)
obj['valid_not_after'] = x509.format_datetime(
@@ -506,10 +507,6 @@ class BaseCertObject(Object):
logger.warning(
"Encountered bad GeneralName; skipping", exc_info=True)
serial_number = obj.get('serial_number')
if serial_number is not None:
obj['serial_number_hex'] = u'0x%X' % serial_number
def _add_san_attribute(self, obj, full, gn):
name_type_map = {
cryptography.x509.RFC822Name:
@@ -584,7 +581,7 @@ class certreq(BaseCertObject):
label=_('Request status'),
flags={'no_create', 'no_update', 'no_search'},
),
Int(
Str(
'request_id',
label=_('Request id'),
primary_key=True,
@@ -954,7 +951,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
reason=e,
)
)
result['request_id'] = int(result['request_id'])
result['request_id'] = result['request_id']
result['cacn'] = ca_obj['cn'][0]
# Success? Then add it to the principal's entry
@@ -985,7 +982,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
return dict(
result=result,
value=pkey_to_value(int(result['request_id']), kw),
value=pkey_to_value(result['request_id'], kw),
)
def lookup_principal(self, principal):