2008-12-21 15:15:53 -06:00
# Authors:
# Andrew Wnuk <awnuk@redhat.com>
# Jason Gerard DeRose <jderose@redhat.com>
2009-12-08 15:57:07 -06:00
# John Dennis <jdennis@redhat.com>
2008-12-21 15:15:53 -06:00
#
# Copyright (C) 2009 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
2009-02-05 16:30:58 -06:00
Command plugins for IPA - RA certificate operations .
2008-12-21 15:15:53 -06:00
"""
2009-02-12 03:10:12 -06:00
from ipalib import api , SkipPluginModule
if api . env . enable_ra is not True :
2009-02-12 18:18:54 -06:00
# In this case, abort loading this plugin module...
raise SkipPluginModule ( reason = ' env.enable_ra is not True ' )
2009-11-06 04:04:00 -06:00
from ipalib import Command , Str , Int , Bytes , Flag , File
2009-05-05 14:18:33 -05:00
from ipalib import errors
2009-11-24 15:07:44 -06:00
from ipalib import pkcs10
from ipalib import x509
2009-07-10 15:40:39 -05:00
from ipalib . plugins . virtual import *
2009-10-20 10:59:07 -05:00
from ipalib . plugins . service import split_principal
2009-05-05 14:18:33 -05:00
import base64
2009-10-20 10:59:07 -05:00
from ipalib . request import context
from ipapython import dnsclient
2009-11-24 15:07:44 -06:00
from pyasn1 . error import PyAsn1Error
import logging
import traceback
2009-12-08 15:57:07 -06:00
from ipalib . request import ugettext as _
2009-09-10 15:15:14 -05:00
def get_serial ( certificate ) :
"""
Given a certificate , return the serial number in that cert
2009-12-08 15:57:07 -06:00
as a Python long object .
2009-09-10 15:15:14 -05:00
In theory there should be only one cert per object so even if we get
passed in a list / tuple only return the first one .
"""
if type ( certificate ) in ( list , tuple ) :
certificate = certificate [ 0 ]
try :
2009-12-08 15:57:07 -06:00
serial = x509 . get_serial_number ( certificate )
2009-11-24 15:07:44 -06:00
except PyAsn1Error :
2009-12-08 15:57:07 -06:00
raise errors . CertificateOperationError ( error = _ ( ' Unable to decode certificate in entry ' ) )
2009-09-10 15:15:14 -05:00
return serial
2009-05-05 14:18:33 -05:00
2009-10-20 10:59:07 -05:00
def get_csr_hostname ( csr ) :
"""
Return the value of CN in the subject of the request
"""
try :
2009-11-24 15:07:44 -06:00
request = pkcs10 . load_certificate_request ( csr )
2009-10-20 10:59:07 -05:00
sub = request . get_subject ( ) . get_components ( )
for s in sub :
if s [ 0 ] . lower ( ) == " cn " :
return s [ 1 ]
2009-11-24 15:07:44 -06:00
except PyAsn1Error :
# The ASN.1 decoding errors tend to be long and involved and the
# last bit is generally not interesting. We need the whole traceback.
logging . error ( ' Unable to decode CSR \n %s ' , traceback . format_exc ( ) )
2009-12-08 15:57:07 -06:00
raise errors . CertificateOperationError ( error = _ ( ' Failure decoding Certificate Signing Request ' ) )
2009-10-20 10:59:07 -05:00
return None
2009-11-24 15:07:44 -06:00
def get_subjectaltname ( csr ) :
"""
Return the value of the subject alt name , if any
"""
try :
request = pkcs10 . load_certificate_request ( csr )
except PyAsn1Error :
# The ASN.1 decoding errors tend to be long and involved and the
# last bit is generally not interesting. We need the whole traceback.
logging . error ( ' Unable to decode CSR \n %s ' , traceback . format_exc ( ) )
2009-12-08 15:57:07 -06:00
raise errors . CertificateOperationError ( error = _ ( ' Failure decoding Certificate Signing Request ' ) )
2009-11-24 15:07:44 -06:00
return request . get_subjectaltname ( )
2009-05-05 14:18:33 -05:00
def validate_csr ( ugettext , csr ) :
"""
2009-11-24 15:07:44 -06:00
Ensure the CSR is base64 - encoded and can be decoded by our PKCS #10
parser .
2009-05-05 14:18:33 -05:00
"""
try :
2009-11-24 15:07:44 -06:00
request = pkcs10 . load_certificate_request ( csr )
# Explicitly request the attributes. This fires off additional
# decoding to get things like the subjectAltName.
attrs = request . get_attributes ( )
except TypeError , e :
2009-05-05 14:18:33 -05:00
raise errors . Base64DecodeError ( reason = str ( e ) )
2009-11-24 15:07:44 -06:00
except PyAsn1Error :
2009-12-08 15:57:07 -06:00
raise errors . CertificateOperationError ( error = _ ( ' Failure decoding Certificate Signing Request ' ) )
2009-11-24 15:07:44 -06:00
except Exception , e :
2009-12-08 15:57:07 -06:00
raise errors . CertificateOperationError ( error = _ ( ' Failure decoding Certificate Signing Request: %s ' ) % str ( e ) )
2008-12-21 15:15:53 -06:00
2009-07-10 15:40:39 -05:00
class cert_request ( VirtualCommand ) :
2009-02-12 03:10:12 -06:00
"""
2009-10-20 10:59:07 -05:00
Submit a certificate signing request .
2009-02-12 03:10:12 -06:00
"""
2008-12-21 15:15:53 -06:00
2009-11-06 04:04:00 -06:00
takes_args = (
File ( ' csr ' , validate_csr ,
cli_name = ' csr_file ' ,
) ,
)
2009-07-10 15:40:39 -05:00
operation = " request certificate "
2008-12-21 15:15:53 -06:00
2009-02-12 03:10:12 -06:00
takes_options = (
2009-05-05 14:18:33 -05:00
Str ( ' principal ' ,
doc = " service principal for this certificate (e.g. HTTP/test.example.com) " ,
) ,
Str ( ' request_type ' ,
default = u ' pkcs10 ' ,
autofill = True ,
) ,
Flag ( ' add ' ,
doc = " automatically add the principal if it doesn ' t exist " ,
default = False ,
autofill = True
) ,
2009-02-12 03:10:12 -06:00
)
2008-12-21 15:15:53 -06:00
2009-05-05 14:18:33 -05:00
def execute ( self , csr , * * kw ) :
2009-11-03 08:35:19 -06:00
ldap = self . api . Backend . ldap2
2009-05-05 14:18:33 -05:00
principal = kw . get ( ' principal ' )
add = kw . get ( ' add ' )
del kw [ ' principal ' ]
del kw [ ' add ' ]
service = None
2009-11-24 15:07:44 -06:00
"""
Access control is partially handled by the ACI titled
' Hosts can modify service userCertificate ' . This is for the case
where a machine binds using a host / prinicpal . It can only do the
request if the target hostname is in the managedBy attribute which
is managed using the add / del member commands .
Binding with a user principal one needs to be in the request_certs
taskgroup ( directly or indirectly via role membership ) .
"""
2009-11-06 04:04:00 -06:00
2009-10-20 10:59:07 -05:00
# Can this user request certs?
self . check_access ( )
# FIXME: add support for subject alt name
# Ensure that the hostname in the CSR matches the principal
2009-11-24 15:07:44 -06:00
subject_host = get_csr_hostname ( csr )
2009-10-20 10:59:07 -05:00
( servicename , hostname , realm ) = split_principal ( principal )
if subject_host . lower ( ) != hostname . lower ( ) :
raise errors . ACIError ( info = " hostname in subject of request ' %s ' does not match principal hostname ' %s ' " % ( subject_host , hostname ) )
2009-11-24 15:07:44 -06:00
dn = None
service = None
2009-05-05 14:18:33 -05:00
# See if the service exists and punt if it doesn't and we aren't
# going to add it
try :
2009-11-24 15:07:44 -06:00
( dn , service ) = api . Command [ ' service_show ' ] ( principal , all = True , raw = True )
2009-09-10 15:15:14 -05:00
if ' usercertificate ' in service :
2009-05-05 14:18:33 -05:00
# FIXME, what to do here? Do we revoke the old cert?
2009-12-08 15:57:07 -06:00
raise errors . CertificateOperationError ( error = _ ( ' entry already has a certificate, serial number %s ' ) % get_serial ( base64 . b64encode ( service [ ' usercertificate ' ] [ 0 ] ) ) )
2009-05-05 14:18:33 -05:00
except errors . NotFound , e :
if not add :
2009-09-10 15:15:14 -05:00
raise errors . NotFound ( reason = " The service principal for this request doesn ' t exist. " )
2009-11-03 08:35:19 -06:00
try :
( dn , service ) = api . Command [ ' service_add ' ] ( principal , * * { } )
except errors . ACIError :
raise errors . ACIError ( info = ' You need to be a member of the serviceadmin role to add services ' )
# We got this far so the service entry exists, can we write it?
if not ldap . can_write ( dn , " usercertificate " ) :
raise errors . ACIError ( info = " Insufficient ' write ' privilege to the ' userCertificate ' attribute of entry ' %s ' . " % dn )
2009-05-05 14:18:33 -05:00
2009-11-24 15:07:44 -06:00
# Validate the subject alt name, if any
subjectaltname = get_subjectaltname ( csr )
if subjectaltname is not None :
for name in subjectaltname :
try :
( hostdn , hostentry ) = api . Command [ ' host_show ' ] ( name , all = True , raw = True )
except errors . NotFound :
# We don't want to issue any certificates referencing
# machines we don't know about. Nothing is stored in this
# host record related to this certificate.
raise errors . NotFound ( reason = ' no host record for subject alt name %s in certificate request ' % name )
authprincipal = getattr ( context , ' principal ' )
if authprincipal . startswith ( " host/ " ) :
if not hostdn in service . get ( ' managedby ' , [ ] ) :
raise errors . ACIError ( info = " Insufficient privilege to create a certificate with subject alt name ' %s ' . " % name )
2009-05-05 14:18:33 -05:00
# Request the certificate
result = self . Backend . ra . request_certificate ( csr , * * kw )
2009-11-03 08:35:19 -06:00
# Success? Then add it to the service entry.
if result . get ( ' status ' ) == 0 :
2009-05-05 14:18:33 -05:00
skw = { " usercertificate " : str ( result . get ( ' certificate ' ) ) }
api . Command [ ' service_mod ' ] ( principal , * * skw )
return result
def output_for_cli ( self , textui , result , * args , * * kw ) :
2009-02-12 03:10:12 -06:00
if isinstance ( result , dict ) and len ( result ) > 0 :
textui . print_entry ( result , 0 )
else :
2009-12-08 15:57:07 -06:00
textui . print_plain ( _ ( ' Failed to submit a certificate request. ' ) )
2008-12-21 15:15:53 -06:00
2009-02-12 03:10:12 -06:00
api . register ( cert_request )
2008-12-21 15:15:53 -06:00
2009-07-10 15:40:39 -05:00
class cert_status ( VirtualCommand ) :
2009-02-12 03:10:12 -06:00
"""
Check status of a certificate signing request .
"""
2009-02-05 16:30:58 -06:00
2009-05-21 13:31:54 -05:00
takes_args = ( ' request_id ' )
2009-07-10 15:40:39 -05:00
operation = " certificate status "
2008-12-21 15:15:53 -06:00
2009-05-05 14:18:33 -05:00
def execute ( self , request_id , * * kw ) :
2009-10-20 10:59:07 -05:00
self . check_access ( )
2009-02-12 03:10:12 -06:00
return self . Backend . ra . check_request_status ( request_id )
2008-12-21 15:15:53 -06:00
2009-05-05 14:18:33 -05:00
def output_for_cli ( self , textui , result , * args , * * kw ) :
2009-02-12 03:10:12 -06:00
if isinstance ( result , dict ) and len ( result ) > 0 :
textui . print_entry ( result , 0 )
else :
2009-12-08 15:57:07 -06:00
textui . print_plain ( _ ( ' Failed to retrieve a request status. ' ) )
2008-12-21 15:15:53 -06:00
2009-02-12 03:10:12 -06:00
api . register ( cert_status )
2008-12-21 15:15:53 -06:00
2009-07-10 15:40:39 -05:00
class cert_get ( VirtualCommand ) :
2009-02-12 03:10:12 -06:00
"""
Retrieve an existing certificate .
"""
2008-12-21 15:15:53 -06:00
2009-12-08 15:57:07 -06:00
takes_args = ( Str ( ' serial_number ' ,
doc = ' serial number in decimal or if prefixed with 0x in hexadecimal ' ) )
2009-07-10 15:40:39 -05:00
operation = " retrieve certificate "
2008-12-21 15:15:53 -06:00
2009-02-12 03:10:12 -06:00
def execute ( self , serial_number ) :
2009-10-20 10:59:07 -05:00
self . check_access ( )
2009-02-12 03:10:12 -06:00
return self . Backend . ra . get_certificate ( serial_number )
2008-12-21 15:15:53 -06:00
2009-05-05 14:18:33 -05:00
def output_for_cli ( self , textui , result , * args , * * kw ) :
2009-02-12 03:10:12 -06:00
if isinstance ( result , dict ) and len ( result ) > 0 :
textui . print_entry ( result , 0 )
else :
2009-12-08 15:57:07 -06:00
textui . print_plain ( _ ( ' Failed to obtain a certificate. ' ) )
2008-12-21 15:15:53 -06:00
2009-02-12 03:10:12 -06:00
api . register ( cert_get )
2008-12-21 15:15:53 -06:00
2009-07-10 15:40:39 -05:00
class cert_revoke ( VirtualCommand ) :
2009-02-12 03:10:12 -06:00
"""
Revoke a certificate .
"""
2008-12-21 15:15:53 -06:00
2009-12-08 15:57:07 -06:00
takes_args = ( Str ( ' serial_number ' ,
doc = ' serial number in decimal or if prefixed with 0x in hexadecimal ' ) )
2009-07-10 15:40:39 -05:00
operation = " revoke certificate "
2008-12-21 15:15:53 -06:00
2009-02-12 03:10:12 -06:00
# FIXME: The default is 0. Is this really an Int param?
2009-05-08 13:10:53 -05:00
takes_options = (
Int ( ' revocation_reason? ' ,
doc = ' Reason for revoking the certificate (0-10) ' ,
minvalue = 0 ,
maxvalue = 10 ,
default = 0 ,
) ,
)
2008-12-21 15:15:53 -06:00
2009-05-05 14:18:33 -05:00
def execute ( self , serial_number , * * kw ) :
2009-10-20 10:59:07 -05:00
self . check_access ( )
2009-05-05 14:18:33 -05:00
return self . Backend . ra . revoke_certificate ( serial_number , * * kw )
2008-12-21 15:15:53 -06:00
2009-05-05 14:18:33 -05:00
def output_for_cli ( self , textui , result , * args , * * kw ) :
2009-02-12 03:10:12 -06:00
if isinstance ( result , dict ) and len ( result ) > 0 :
textui . print_entry ( result , 0 )
else :
2009-12-08 15:57:07 -06:00
textui . print_plain ( _ ( ' Failed to revoke a certificate. ' ) )
2008-12-21 15:15:53 -06:00
2009-02-12 03:10:12 -06:00
api . register ( cert_revoke )
2008-12-21 15:15:53 -06:00
2009-07-10 15:40:39 -05:00
class cert_remove_hold ( VirtualCommand ) :
2009-02-12 03:10:12 -06:00
"""
Take a revoked certificate off hold .
"""
2008-12-21 15:15:53 -06:00
2009-12-08 15:57:07 -06:00
takes_args = ( Str ( ' serial_number ' ,
doc = ' serial number in decimal or if prefixed with 0x in hexadecimal ' ) )
2009-07-10 15:40:39 -05:00
operation = " certificate remove hold "
2008-12-21 15:15:53 -06:00
2009-05-05 14:18:33 -05:00
def execute ( self , serial_number , * * kw ) :
2009-10-20 10:59:07 -05:00
self . check_access ( )
2009-02-12 03:10:12 -06:00
return self . Backend . ra . take_certificate_off_hold ( serial_number )
2009-05-05 14:18:33 -05:00
def output_for_cli ( self , textui , result , * args , * * kw ) :
2009-02-12 03:10:12 -06:00
if isinstance ( result , dict ) and len ( result ) > 0 :
textui . print_entry ( result , 0 )
else :
2009-12-08 15:57:07 -06:00
textui . print_plain ( _ ( ' Failed to take a revoked certificate off hold. ' ) )
2009-02-12 03:10:12 -06:00
api . register ( cert_remove_hold )