Skip to content

Commit e602434

Browse files
authored
Merge pull request #83 from Venafi/vaas_service_generated_csr
Vaas service generated CSR
2 parents 99eedf3 + 169ed0f commit e602434

14 files changed

Lines changed: 573 additions & 122 deletions

examples/get_cert.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
#
3-
# Copyright 2019 Venafi, Inc.
3+
# Copyright 2022 Venafi, Inc.
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.

examples/get_cert27.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python2
22
#
3-
# Copyright 2019 Venafi, Inc.
3+
# Copyright 2022 Venafi, Inc.
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
#
3-
# Copyright 2020 Venafi, Inc.
3+
# Copyright 2022 Venafi, Inc.
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
#
3-
# Copyright 2019 Venafi, Inc.
3+
# Copyright 2022 Venafi, Inc.
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
#
3-
# Copyright 2019 Venafi, Inc.
3+
# Copyright 2022 Venafi, Inc.
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright 2022 Venafi, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
from __future__ import print_function
19+
20+
from vcert import (CertificateRequest, venafi_connection, CSR_ORIGIN_SERVICE, CHAIN_OPTION_FIRST)
21+
import string
22+
import random
23+
import logging
24+
from os import environ
25+
26+
logging.basicConfig(level=logging.INFO)
27+
logging.getLogger("urllib3").setLevel(logging.ERROR)
28+
29+
30+
def main():
31+
# Get credentials from environment variables
32+
url = environ.get('VAAS_URL') # Optional, only use when connecting to a specific VaaS server
33+
api_key = environ.get('VAAS_APIKEY')
34+
zone = environ.get("VAAS_ZONE")
35+
36+
# Connection will be chosen automatically based on which arguments are passed.
37+
# If api_key is passed, Venafi Cloud connection will be used.
38+
# url attribute is no required when connecting to production VaaS platform
39+
conn = venafi_connection(url=url, api_key=api_key)
40+
41+
# Build a Certificate request
42+
request = CertificateRequest(common_name=random_word(10) + ".venafi.example.com")
43+
# Set the request to use a service generated CSR
44+
request.csr_origin = CSR_ORIGIN_SERVICE
45+
# A password should be defined for the private key to be generated.
46+
request.key_password = 'Foo.Bar.Pass.123!'
47+
# Include some Subject Alternative Names
48+
request.san_dns = ["www.dns.venafi.example.com", "ww1.dns.venafi.example.com"]
49+
# Additional CSR attributes can be included:
50+
request.organization = "Venafi, Inc."
51+
request.organizational_unit = ["Product Management"]
52+
request.locality = "Salt Lake City"
53+
request.province = "Utah" # This is the same as state
54+
request.country = "US"
55+
56+
# Specify ordering certificates in chain. Root can be CHAIN_OPTION_FIRST ("first")
57+
# or CHAIN_OPTION_LAST ("last"). By default it is CHAIN_OPTION_LAST.
58+
# request.chain_option = CHAIN_OPTION_FIRST
59+
#
60+
# To set Custom Fields for the certificate, specify an array of CustomField objects as name-value pairs
61+
# request.custom_fields = [
62+
# CustomField(name="Cost Center", value="ABC123"),
63+
# CustomField(name="Environment", value="Production"),
64+
# CustomField(name="Environment", value="Staging")
65+
# ]
66+
#
67+
# Request the certificate.
68+
conn.request_cert(request, zone)
69+
# Wait for the certificate to be retrieved.
70+
# This operation may take some time to return, as it waits until the certificate is ISSUED or it timeout.
71+
# Timeout is 180s by default. Can be changed using:
72+
# request.timeout = 300
73+
cert = conn.retrieve_cert(request)
74+
75+
# Print the certificate
76+
print(cert.full_chain)
77+
# Save it into a file
78+
f = open("./cert.pem", "w")
79+
f.write(cert.full_chain)
80+
f.close()
81+
82+
83+
def random_word(length):
84+
letters = string.ascii_lowercase
85+
return ''.join(random.choice(letters) for i in range(length))
86+
87+
88+
if __name__ == '__main__':
89+
main()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
#
3-
# Copyright 2019 Venafi, Inc.
3+
# Copyright 2022 Venafi, Inc.
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ ipaddress;python_version<'3.3'
66
enum34;python_version<'3.4'
77
future
88
ruamel.yaml<0.17
9+
pynacl>=1.4.0

tests/test_vaas.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
from cryptography import x509
2323
from cryptography.hazmat.backends import default_backend
2424
from cryptography.hazmat.primitives import hashes
25+
from cryptography.x509.oid import NameOID
2526

26-
from test_env import CLOUD_ZONE, CLOUD_APIKEY, CLOUD_URL
27+
from test_env import CLOUD_ZONE, CLOUD_APIKEY, CLOUD_URL, RANDOM_DOMAIN
2728
from test_utils import random_word, enroll, renew, renew_by_thumbprint, renew_without_key_reuse, simple_enroll
28-
from vcert import CloudConnection, KeyType, CertificateRequest, CustomField, logger
29+
from vcert import CloudConnection, KeyType, CertificateRequest, CustomField, logger, CSR_ORIGIN_SERVICE
2930

3031
log = logger.get_child("test-vaas")
3132

@@ -135,3 +136,32 @@ def test_cloud_enroll_valid_hours(self):
135136
"Expected_delta: %s seconds."
136137
% (expected_date.strftime(date_format), expiration_date.strftime(date_format),
137138
delta.total_seconds()))
139+
140+
def test_cloud_enroll_service_generated_csr(self):
141+
cn = random_word(10) + ".venafi.example.com"
142+
password = 'FooBarPass123'
143+
144+
request = CertificateRequest(
145+
common_name=cn,
146+
key_password=password,
147+
country='US'
148+
)
149+
150+
request.san_dns = ["www.client.venafi.example.com", "ww1.client.venafi.example.com"]
151+
request.csr_origin = CSR_ORIGIN_SERVICE
152+
153+
self.cloud_conn.request_cert(request, self.cloud_zone)
154+
cert_object = self.cloud_conn.retrieve_cert(request)
155+
156+
cert = x509.load_pem_x509_certificate(cert_object.cert.encode(), default_backend())
157+
assert isinstance(cert, x509.Certificate)
158+
t1 = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
159+
t2 = [
160+
x509.NameAttribute(
161+
NameOID.COMMON_NAME, cn or RANDOM_DOMAIN
162+
)
163+
]
164+
assert t1 == t2
165+
166+
output = cert_object.as_pkcs12('FooBarPass123')
167+
log.info("PKCS12 created successfully for certificate with CN: %s" % cn)

vcert/common.py

Lines changed: 52 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,20 @@
4040
from .ssh_utils import SSHCertRequest, SSHRetrieveResponse, SSHCATemplateRequest, SSHConfig
4141
from .tpp_utils import IssuerHint
4242

43-
MIME_JSON = "application/json"
44-
MIME_HTML = "text/html"
45-
MIME_TEXT = "text/plain"
46-
MIME_CSV = "text/csv"
47-
MIME_ANY = "*/*"
48-
LOCALHOST = "127.0.0.1"
43+
MIME_JSON = 'application/json'
44+
MIME_HTML = 'text/html'
45+
MIME_TEXT = 'text/plain'
46+
MIME_CSV = 'text/csv'
47+
MIME_ANY = '*/*'
48+
MIME_OCTET_STREAM = 'application/octet-stream'
49+
LOCALHOST = '127.0.0.1'
4950
DEFAULT_TIMEOUT = 180
50-
CSR_ORIGIN_PROVIDED = "provided"
51-
CSR_ORIGIN_LOCAL = "local"
52-
CSR_ORIGIN_SERVICE = "service"
53-
CHAIN_OPTION_FIRST = "first"
54-
CHAIN_OPTION_LAST = "last"
55-
CHAIN_OPTION_IGNORE = "ignore"
51+
CSR_ORIGIN_PROVIDED = 'provided'
52+
CSR_ORIGIN_LOCAL = 'local'
53+
CSR_ORIGIN_SERVICE = 'service'
54+
CHAIN_OPTION_FIRST = 'first'
55+
CHAIN_OPTION_LAST = 'last'
56+
CHAIN_OPTION_IGNORE = 'ignore'
5657

5758

5859
class CertField:
@@ -221,33 +222,32 @@ def __str__(self):
221222
return self.name
222223

223224

224-
class RecommendedSettings:
225-
def __init__(self, subject_o_value=None, subject_ou_value=None, subject_l_value=None, subject_st_value=None,
226-
subject_c_value=None, key_type=None, key_reuse=None):
227-
"""
228-
:param str subject_o_value:
229-
:param str subject_ou_value:
230-
:param str subject_l_value:
231-
:param str subject_st_value:
232-
:param str subject_c_value:
233-
:param KeyType key_type:
234-
:param bool key_reuse:
235-
"""
236-
self.subjectOValue = subject_o_value
237-
self.subjectOUValue = subject_ou_value
238-
self.subjectLValue = subject_l_value
239-
self.subjectSTValue = subject_st_value
240-
self.subjectCValue = subject_c_value
241-
self.keyType = key_type
242-
self.keyReuse = key_reuse
243-
244-
245225
class CertificateRequest:
246-
def __init__(self, cert_id=None, san_dns=None, email_addresses="", ip_addresses=None, user_principal_names=None,
247-
uniform_resource_identifiers=None, attributes=None, key_type=None, private_key=None, key_password=None,
248-
csr=None, friendly_name=None, common_name=None, thumbprint=None, organization=None,
249-
organizational_unit=None, country=None, province=None, locality=None, origin=None, custom_fields=None,
250-
timeout=DEFAULT_TIMEOUT, csr_origin=CSR_ORIGIN_LOCAL, include_private_key=False, validity_hours=None,
226+
def __init__(self, cert_id=None,
227+
san_dns=None,
228+
email_addresses="",
229+
ip_addresses=None,
230+
user_principal_names=None,
231+
uniform_resource_identifiers=None,
232+
attributes=None,
233+
key_type=None,
234+
private_key=None,
235+
key_password=None,
236+
csr=None,
237+
friendly_name=None,
238+
common_name=None,
239+
thumbprint=None,
240+
organization=None,
241+
organizational_unit=None,
242+
country=None,
243+
province=None,
244+
locality=None,
245+
origin=None,
246+
custom_fields=None,
247+
timeout=DEFAULT_TIMEOUT,
248+
csr_origin=CSR_ORIGIN_LOCAL,
249+
include_private_key=False,
250+
validity_hours=None,
251251
issuer_hint=IssuerHint.DEFAULT):
252252
"""
253253
:param str cert_id: Certificate request id. Generating by server.
@@ -259,11 +259,16 @@ def __init__(self, cert_id=None, san_dns=None, email_addresses="", ip_addresses=
259259
:param attributes:
260260
:param KeyType key_type: Type of asymmetric cryptography algorithm. Default is RSA 2048.
261261
:param asymmetric.PrivateKey private_key: String with pem encoded private key or asymmetric.PrivateKey
262-
:param str key_password: Password for encrypted private key. Not supported at this moment.
262+
:param str key_password: Password for encrypted private key.
263263
:param str csr: Certificate Signing Request in pem format
264264
:param str friendly_name: Name for certificate in the platform. If not specified common name will be used.
265265
:param str common_name: Common name of certificate. Usually domain name.
266266
:param str thumbprint: Certificate thumbprint. Can be used for identifying certificate on the platform.
267+
:param organization:
268+
:param organizational_unit:
269+
:param country:
270+
:param province:
271+
:param locality:
267272
:param str origin: application identifier
268273
:param list[CustomField] custom_fields: list of custom fields values to be added to the certificate.
269274
:param int timeout: Timeout for the certificate to be retrieved from server. Measured in seconds.
@@ -582,32 +587,6 @@ def __init__(self, user=None, password=None, access_token=None, refresh_token=No
582587
self.state = state
583588

584589

585-
class AppDetails:
586-
def __init__(self, app_id=None, cit_map=None, company_id=None, name=None, description=None,
587-
owner_ids_and_types=None, fq_dns=None, internal_fq_dns=None, external_ip_ranges=None,
588-
internal_ip_ranges=None, internal_ports=None, fully_qualified_domain_names=None, ip_ranges=None,
589-
ports=None, org_unit_id=None):
590-
"""
591-
:param str app_id:
592-
:param dict cit_map:
593-
"""
594-
self.app_id = app_id
595-
self.cit_alias_id_map = cit_map
596-
self.company_id = company_id
597-
self.name = name
598-
self.description = description
599-
self.owner_ids_and_types = owner_ids_and_types
600-
self.fq_dns = fq_dns
601-
self.internal_fq_dns = internal_fq_dns
602-
self.external_ip_ranges = external_ip_ranges
603-
self.internal_ip_ranges = internal_ip_ranges
604-
self.internal_ports = internal_ports
605-
self.fully_qualified_domain_names = fully_qualified_domain_names
606-
self.ip_ranges = ip_ranges
607-
self.ports = ports
608-
self.org_unit_id = org_unit_id
609-
610-
611590
class CommonConnection:
612591

613592
def auth(self):
@@ -702,6 +681,11 @@ def retrieve_ssh_config(self, ca_request):
702681

703682
@staticmethod
704683
def process_server_response(r):
684+
"""
685+
686+
:param requests.Response r:
687+
:rtype: str or dict
688+
"""
705689
if r.status_code not in (HTTPStatus.OK, HTTPStatus.ACCEPTED, HTTPStatus.CREATED, HTTPStatus.CONFLICT):
706690
try:
707691
log_errors(r.json())
@@ -727,6 +711,9 @@ def process_server_response(r):
727711
elif content_type.startswith(MIME_CSV):
728712
log.debug(r.content.decode())
729713
return r.status_code, r.content.decode()
714+
elif content_type.startswith(MIME_OCTET_STREAM):
715+
log.debug(r.content)
716+
return r.status_code, r.content
730717
else:
731718
log.error("Unexpected content type: %s for request %s" % (content_type, r.request.url))
732719
raise ServerUnexptedBehavior

0 commit comments

Comments
 (0)