2015-06-22 22:47:27 +02:00
"""
2016-12-14 18:29:04 +01:00
. . module : lemur . plugins . lemur_verisign . plugin
2015-06-22 22:47:27 +02:00
: platform : Unix
: synopsis : This module is responsible for communicating with the VeriSign VICE 2.0 API .
2018-05-29 19:18:16 +02:00
: copyright : ( c ) 2018 by Netflix Inc . , see AUTHORS for more
2015-06-22 22:47:27 +02:00
: license : Apache , see LICENSE for more details .
. . moduleauthor : : Kevin Glisson < kglisson @netflix.com >
"""
import arrow
import requests
import xmltodict
2018-10-01 18:20:50 +02:00
from cryptography import x509
2015-06-22 22:47:27 +02:00
from flask import current_app
2018-10-01 18:20:50 +02:00
from lemur . common . utils import get_psuedo_random_string
2019-04-26 04:14:15 +02:00
from lemur . extensions import metrics , sentry
2015-07-04 21:47:57 +02:00
from lemur . plugins import lemur_verisign as verisign
2016-12-02 22:02:59 +01:00
from lemur . plugins . bases import IssuerPlugin , SourcePlugin
2015-06-22 22:47:27 +02:00
# https://support.venafi.com/entries/66445046-Info-VeriSign-Error-Codes
VERISIGN_ERRORS = {
" 0x30c5 " : " Domain Mismatch when enrolling for an SSL certificate, a domain in your request has not been added to verisign " ,
" 0x3a10 " : " Invalid X509 certificate format.: an unsupported certificate format was submitted " ,
" 0x4002 " : " Internal QM Error. : Internal Database connection error. " ,
" 0x3301 " : " Bad transaction id or parent cert not renewable.: User try to renew a certificate that is not yet ready for renew or the transaction id is wrong " ,
" 0x3069 " : " Challenge phrase mismatch: The challenge phrase submitted does not match the original one " ,
" 0x3111 " : " Unsupported Product: User submitted a wrong product or requested cipher is not supported " ,
" 0x30e8 " : " CN or org does not match the original one.: the submitted CSR contains a common name or org that does not match the original one " ,
" 0x1005 " : " Duplicate certificate: a certificate with the same common name exists already " ,
" 0x0194 " : " Incorrect Signature Algorithm: The requested signature algorithm is not supported for the key type. i.e. an ECDSA is submitted for an RSA key " ,
" 0x6000 " : " parameter missing or incorrect: This is a general error code for missing or incorrect parameters. The reason will be in the response message. i.e. ' CSR is missing, ' Unsupported serverType ' when no supported serverType could be found., ' invalid transaction id ' " ,
" 0x3063 " : " Certificate not allowed: trying to issue a certificate that is not configured for the account " ,
" 0x23df " : " No MDS Data Returned: internal connection lost or server not responding. this should be rare " ,
" 0x3004 " : " Invalid Account: The users mpki account associated with the certificate is not valid or not yet active " ,
" 0x4101 " : " Internal Error: internal server error, user should try again later. (Also check that State is spelled out " ,
" 0x3101 " : " Missing admin role: Your account does not have the admin role required to access the webservice API " ,
" 0x3085 " : " Account does not have webservice feature.: Your account does not the the webservice role required to access the webservice API " ,
" 0x9511 " : " Corrupted CSR : the submitted CSR was mal-formed " ,
" 0xa001 " : " Public key format does not match.: The public key format does not match the original cert at certificate renewal or replacement. E.g. if you try to renew or replace an RSA cert with a DSA or ECC key based CSR " ,
" 0x0143 " : " Certificate End Date Error: You are trying to replace a certificate with validity end date exceeding the original cert. or the certificate end date is not valid " ,
" 0x482d " : " SHA1 validity check error: What error code do we get when we submit the SHA1 SSL requests with the validity more than 12/31/2016? " ,
" 0x482e " : " What error code do we get when we cannot complete the re-authentication for domains with a newly-approved gTLD 30 days after the gTLD approval " ,
" 0x4824 " : " Per CA/B Forum baseline requirements, non-FQDN certs cannot exceed 11/1/2015. Examples: hostname, foo.cba (.cba is a pending gTLD) " ,
" eE0x48 " : " Currently the maximum cert validity is 4-years " ,
" 0x4826 " : " OU misleading. See comments " ,
" 0x4827 " : " Org re-auth past due. EV org has to go through re-authentication every 13 months; OV org has to go through re-authentication every 39 months " ,
" 0x482a " : " Domain re-auth past due. EV domain has to go through re-authentication every 13 months; OV domain has to go through re-authentication every 39 months. " ,
" 0x482b " : " No org address was set to default, should not happen " ,
" 0x482c " : " signature algorithm does not match intended key type in the CSR (e.g. CSR has an ECC key, but the signature algorithm is sha1WithRSAEncryption) " ,
" 0x600E " : " only supports ECC keys with the named curve NIST P-256, aka secp256r1 or prime256v1, other ECC key sizes will get this error " ,
" 0x6013 " : " only supports DSA keys with (2048, 256) as the bit lengths of the prime parameter pair (p, q), other DSA key sizes will get this error " ,
" 0x600d " : " RSA key size < 2A048 " ,
" 0x4828 " : " Verisign certificates can be at most two years in length " ,
2015-07-09 01:41:45 +02:00
" 0x3043 " : " Certificates must have a validity of at least 1 day " ,
" 0x950b " : " CSR: Invalid State " ,
2015-08-24 18:43:30 +02:00
" 0x3105 " : " Organization Name Not Matched " ,
2016-10-14 00:23:56 +02:00
" 0x300a " : " Domain/SubjectAltName Mismatched -- make sure that the SANs have the proper domain suffix " ,
" 0x950e " : " Invalid Common Name -- make sure the CN has a proper domain suffix " ,
2017-10-24 23:46:33 +02:00
" 0xa00e " : " Pending. (Insufficient number of tokens.) " ,
2019-05-16 16:57:02 +02:00
" 0x8134 " : " Pending. (Domain failed CAA validation.) " ,
2015-06-22 22:47:27 +02:00
}
2016-12-02 22:02:59 +01:00
def log_status_code ( r , * args , * * kwargs ) :
"""
Is a request hook that logs all status codes to the verisign api .
: param r :
: param args :
: param kwargs :
: return :
"""
2019-05-16 16:57:02 +02:00
metrics . send ( " symantec_status_code_ {} " . format ( r . status_code ) , " counter " , 1 )
2016-12-02 22:02:59 +01:00
2017-01-28 06:05:25 +01:00
def get_additional_names ( options ) :
"""
Return a list of strings to be added to a SAN certificates .
: param options :
: return :
"""
names = [ ]
# add SANs if present
2019-05-16 16:57:02 +02:00
if options . get ( " extensions " ) :
for san in options [ " extensions " ] [ " sub_alt_names " ] :
2017-01-28 06:05:25 +01:00
if isinstance ( san , x509 . DNSName ) :
names . append ( san . value )
return names
2015-07-23 17:52:56 +02:00
def process_options ( options ) :
"""
Processes and maps the incoming issuer options to fields / options that
verisign understands
: param options :
: return : dict or valid verisign options
"""
data = {
2019-05-16 16:57:02 +02:00
" challenge " : get_psuedo_random_string ( ) ,
" serverType " : " Apache " ,
" certProductType " : " Server " ,
" firstName " : current_app . config . get ( " VERISIGN_FIRST_NAME " ) ,
" lastName " : current_app . config . get ( " VERISIGN_LAST_NAME " ) ,
" signatureAlgorithm " : " sha256WithRSAEncryption " ,
" email " : current_app . config . get ( " VERISIGN_EMAIL " ) ,
" ctLogOption " : current_app . config . get ( " VERISIGN_CS_LOG_OPTION " , " public " ) ,
2015-07-23 17:52:56 +02:00
}
2019-05-16 16:57:02 +02:00
data [ " subject_alt_names " ] = " , " . join ( get_additional_names ( options ) )
2020-02-14 07:50:18 +01:00
2019-05-16 16:57:02 +02:00
if options . get ( " validity_end " ) :
2019-01-30 01:17:08 +01:00
# VeriSign (Symantec) only accepts strictly smaller than 2 year end date
2019-09-20 22:49:38 +02:00
if options . get ( " validity_end " ) < arrow . utcnow ( ) . shift ( years = 2 , days = - 1 ) :
2019-01-30 01:17:08 +01:00
period = get_default_issuance ( options )
2019-05-16 16:57:02 +02:00
data [ " specificEndDate " ] = options [ " validity_end " ] . format ( " MM/DD/YYYY " )
data [ " validityPeriod " ] = period
2019-01-30 01:17:08 +01:00
else :
# allowing Symantec website setting the end date, given the validity period
2019-05-16 16:57:02 +02:00
data [ " validityPeriod " ] = str ( get_default_issuance ( options ) )
options . pop ( " validity_end " , None )
2015-07-23 17:52:56 +02:00
2019-05-16 16:57:02 +02:00
elif options . get ( " validity_years " ) :
if options [ " validity_years " ] in [ 1 , 2 ] :
data [ " validityPeriod " ] = str ( options [ " validity_years " ] ) + " Y "
2016-04-01 23:27:57 +02:00
else :
2019-05-16 16:57:02 +02:00
raise Exception (
" Verisign issued certificates cannot exceed two years in validity "
)
2016-04-01 23:27:57 +02:00
2015-07-23 17:52:56 +02:00
return data
def get_default_issuance ( options ) :
"""
Gets the default time range for certificates
: param options :
: return :
"""
now = arrow . utcnow ( )
2019-09-20 22:49:38 +02:00
if options [ " validity_end " ] < now . shift ( years = + 1 ) :
2019-05-16 16:57:02 +02:00
validity_period = " 1Y "
2019-09-20 22:49:38 +02:00
elif options [ " validity_end " ] < now . shift ( years = + 2 ) :
2019-05-16 16:57:02 +02:00
validity_period = " 2Y "
2015-07-23 17:52:56 +02:00
else :
2019-05-16 16:57:02 +02:00
raise Exception (
" Verisign issued certificates cannot exceed two years in validity "
)
2015-07-23 17:52:56 +02:00
2016-11-09 19:56:22 +01:00
return validity_period
2015-07-23 17:52:56 +02:00
2015-07-09 01:41:45 +02:00
def handle_response ( content ) :
"""
2015-07-23 17:52:56 +02:00
Helper function for parsing responses from the Verisign API .
2015-07-09 01:41:45 +02:00
: param content :
: return : : raise Exception :
"""
d = xmltodict . parse ( content )
global VERISIGN_ERRORS
2019-05-16 16:57:02 +02:00
if d . get ( " Error " ) :
status_code = d [ " Error " ] [ " StatusCode " ]
elif d . get ( " Response " ) :
status_code = d [ " Response " ] [ " StatusCode " ]
2015-07-09 01:41:45 +02:00
if status_code in VERISIGN_ERRORS . keys ( ) :
raise Exception ( VERISIGN_ERRORS [ status_code ] )
return d
2015-07-11 02:09:22 +02:00
class VerisignIssuerPlugin ( IssuerPlugin ) :
2019-05-16 16:57:02 +02:00
title = " Verisign "
slug = " verisign-issuer "
description = " Enables the creation of certificates by the VICE2.0 verisign API. "
2015-06-22 22:47:27 +02:00
version = verisign . VERSION
2019-05-16 16:57:02 +02:00
author = " Kevin Glisson "
author_url = " https://github.com/netflix/lemur.git "
2015-06-22 22:47:27 +02:00
def __init__ ( self , * args , * * kwargs ) :
self . session = requests . Session ( )
2019-05-16 16:57:02 +02:00
self . session . cert = current_app . config . get ( " VERISIGN_PEM_PATH " )
2016-12-02 22:02:59 +01:00
self . session . hooks = dict ( response = log_status_code )
2015-07-11 02:09:22 +02:00
super ( VerisignIssuerPlugin , self ) . __init__ ( * args , * * kwargs )
2015-06-22 22:47:27 +02:00
def create_certificate ( self , csr , issuer_options ) :
"""
Creates a Verisign certificate .
: param csr :
: param issuer_options :
: return : : raise Exception :
"""
2019-05-16 16:57:02 +02:00
url = current_app . config . get ( " VERISIGN_URL " ) + " /rest/services/enroll "
2015-06-22 22:47:27 +02:00
2015-07-23 17:52:56 +02:00
data = process_options ( issuer_options )
2019-05-16 16:57:02 +02:00
data [ " csr " ] = csr
2015-06-22 22:47:27 +02:00
2019-05-16 16:57:02 +02:00
current_app . logger . info (
" Requesting a new verisign certificate: {0} " . format ( data )
)
2015-06-22 22:47:27 +02:00
response = self . session . post ( url , data = data )
2019-04-26 04:14:15 +02:00
try :
2019-05-16 16:57:02 +02:00
cert = handle_response ( response . content ) [ " Response " ] [ " Certificate " ]
2019-04-26 04:14:15 +02:00
except KeyError :
2019-05-16 16:57:02 +02:00
metrics . send (
" verisign_create_certificate_error " ,
" counter " ,
1 ,
metric_tags = { " common_name " : issuer_options . get ( " common_name " , " " ) } ,
)
sentry . captureException (
extra = { " common_name " : issuer_options . get ( " common_name " , " " ) }
)
2019-04-26 04:14:15 +02:00
raise Exception ( f " Error with Verisign: { response . content } " )
2017-09-29 03:27:56 +02:00
# TODO add external id
2019-05-16 16:57:02 +02:00
return cert , current_app . config . get ( " VERISIGN_INTERMEDIATE " ) , None
2015-06-22 22:47:27 +02:00
@staticmethod
def create_authority ( options ) :
"""
Creates an authority , this authority is then used by Lemur to allow a user
to specify which Certificate Authority they want to sign their certificate .
: param options :
: return :
"""
2019-05-16 16:57:02 +02:00
role = { " username " : " " , " password " : " " , " name " : " verisign " }
return current_app . config . get ( " VERISIGN_ROOT " ) , " " , [ role ]
2015-06-22 22:47:27 +02:00
def get_available_units ( self ) :
"""
2016-12-14 18:29:04 +01:00
Uses the Verisign to fetch the number of available units left . This can be used to get tabs
2015-06-22 22:47:27 +02:00
on the number of certificates that can be issued .
: return :
"""
2019-05-16 16:57:02 +02:00
url = current_app . config . get ( " VERISIGN_URL " ) + " /rest/services/getTokens "
response = self . session . post (
url , headers = { " content-type " : " application/x-www-form-urlencoded " }
)
return handle_response ( response . content ) [ " Response " ] [ " Order " ]
2015-09-02 23:37:07 +02:00
2017-12-04 19:04:12 +01:00
def clear_pending_certificates ( self ) :
"""
Uses Verisign to clear the pending certificates awaiting approval .
: return :
"""
2019-05-16 16:57:02 +02:00
url = current_app . config . get ( " VERISIGN_URL " ) + " /reportingws "
2017-12-04 19:04:12 +01:00
end = arrow . now ( )
2019-09-20 22:49:38 +02:00
start = end . shift ( days = - 7 )
2017-12-04 19:04:12 +01:00
data = {
2019-05-16 16:57:02 +02:00
" reportType " : " detail " ,
" certProductType " : " Server " ,
" certStatus " : " Pending " ,
" startDate " : start . format ( " MM/DD/YYYY " ) ,
" endDate " : end . format ( " MM/DD/YYYY " ) ,
2017-12-04 19:04:12 +01:00
}
response = self . session . post ( url , data = data )
2019-05-16 16:57:02 +02:00
url = current_app . config . get ( " VERISIGN_URL " ) + " /rest/services/reject "
for order_id in response . json ( ) [ " orderNumber " ] :
response = self . session . get ( url , params = { " transaction_id " : order_id } )
2017-12-04 19:04:12 +01:00
if response . status_code == 200 :
print ( " Rejecting certificate. TransactionId: {} " . format ( order_id ) )
2015-09-02 23:37:07 +02:00
class VerisignSourcePlugin ( SourcePlugin ) :
2019-05-16 16:57:02 +02:00
title = " Verisign "
slug = " verisign-source "
description = (
" Allows for the polling of issued certificates from the VICE2.0 verisign API. "
)
2015-09-02 23:37:07 +02:00
version = verisign . VERSION
2019-05-16 16:57:02 +02:00
author = " Kevin Glisson "
author_url = " https://github.com/netflix/lemur.git "
2015-09-02 23:37:07 +02:00
def __init__ ( self , * args , * * kwargs ) :
self . session = requests . Session ( )
2019-05-16 16:57:02 +02:00
self . session . cert = current_app . config . get ( " VERISIGN_PEM_PATH " )
2015-09-02 23:37:07 +02:00
super ( VerisignSourcePlugin , self ) . __init__ ( * args , * * kwargs )
def get_certificates ( self ) :
2019-05-16 16:57:02 +02:00
url = current_app . config . get ( " VERISIGN_URL " ) + " /reportingws "
2015-09-02 23:37:07 +02:00
end = arrow . now ( )
2019-09-20 22:49:38 +02:00
start = end . shift ( years = - 5 )
2015-09-02 23:37:07 +02:00
data = {
2019-05-16 16:57:02 +02:00
" reportType " : " detail " ,
" startDate " : start . format ( " MM/DD/YYYY " ) ,
" endDate " : end . format ( " MM/DD/YYYY " ) ,
" structuredRecord " : " Y " ,
" certStatus " : " Valid " ,
2015-09-02 23:37:07 +02:00
}
current_app . logger . debug ( data )
response = self . session . post ( url , data = data )