Skip to content

Commit 805a455

Browse files
author
Matthew Fisher
authored
feat(api): validate a certificate's private key (#1157)
1 parent e470052 commit 805a455

7 files changed

Lines changed: 52 additions & 14 deletions

rootfs/api/models/certificate.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,18 @@ def get_subj_alt_name(peer_cert):
6666

6767
def validate_certificate(value):
6868
try:
69-
crypto.load_certificate(crypto.FILETYPE_PEM, value)
69+
return crypto.load_certificate(crypto.FILETYPE_PEM, value)
7070
except crypto.Error as e:
7171
raise ValidationError('Could not load certificate: {}'.format(e))
7272

7373

74+
def validate_private_key(value):
75+
try:
76+
return crypto.load_privatekey(crypto.FILETYPE_PEM, value)
77+
except crypto.Error as e:
78+
raise ValidationError('Could not load private key: {}'.format(e))
79+
80+
7481
class Certificate(AuditedModel):
7582
"""
7683
Public and private key pair used to secure application traffic at the router.
@@ -79,7 +86,7 @@ class Certificate(AuditedModel):
7986
name = models.CharField(max_length=253, unique=True, validators=[validate_label])
8087
# there is no upper limit on the size of an x.509 certificate
8188
certificate = models.TextField(validators=[validate_certificate])
82-
key = models.TextField()
89+
key = models.TextField(validators=[validate_private_key])
8390
# X.509 certificates allow any string of information as the common name.
8491
common_name = models.TextField(editable=False, unique=False, null=True)
8592
# A list of DNS records if certificate has SubjectAltName
@@ -106,14 +113,14 @@ def domains(self):
106113
def __str__(self):
107114
return self.name
108115

109-
def _get_certificate(self):
116+
def save(self, *args, **kwargs):
110117
try:
111-
return crypto.load_certificate(crypto.FILETYPE_PEM, self.certificate)
112-
except crypto.Error as e:
118+
certificate = validate_certificate(self.certificate)
119+
# NOTE(bacongobbler): we want to load the key here to ensure that it is valid before
120+
# saving it to the database.
121+
validate_private_key(self.key)
122+
except ValidationError as e:
113123
raise SuspiciousOperation(e)
114-
115-
def save(self, *args, **kwargs):
116-
certificate = self._get_certificate()
117124
if not self.common_name:
118125
self.common_name = certificate.get_subject().CN
119126

rootfs/api/tests/test_certificate.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ def test_delete_certificate(self):
145145
name='random-test-cert',
146146
owner=self.user,
147147
common_name='autotest.example.com',
148-
certificate=self.cert
148+
certificate=self.cert,
149+
key=self.key
149150
)
150151
url = '/v2/certs/random-test-cert'
151152
response = self.client.delete(url)
@@ -176,6 +177,31 @@ def test_load_invalid_cert(self):
176177
key='i am bad data as well'
177178
)
178179

180+
def test_create_invalid_key(self):
181+
"""Upload a private key that can't be loaded by pyopenssl"""
182+
response = self.client.post(
183+
self.url,
184+
{
185+
'name': 'random-test-cert',
186+
'certificate': self.cert,
187+
'key': 'I am Groot.'
188+
}
189+
)
190+
self.assertEqual(response.status_code, 400, response.data)
191+
# match partial since right now the rest is pyopenssl errors
192+
self.assertIn('Could not load private key', response.data['key'][0])
193+
194+
def test_load_invalid_key(self):
195+
"""Inject a private key that can't be loaded by pyopenssl"""
196+
197+
with self.assertRaises(SuspiciousOperation):
198+
Certificate.objects.create(
199+
owner=self.user,
200+
name='random-test-cert',
201+
certificate=self.cert,
202+
key='I am Groot.'
203+
)
204+
179205
def test_certs_fetch_limit(self):
180206
"""
181207
When a user retrieves a certificate, make sure limits work

rootfs/api/tests/test_certificate_use_case_1.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ def test_delete_certificate(self):
144144
Certificate.objects.create(
145145
owner=self.user,
146146
name=self.name,
147-
certificate=self.cert
147+
certificate=self.cert,
148+
key=self.key
148149
)
149150

150151
url = '/v2/certs/{}'.format(self.name)

rootfs/api/tests/test_certificate_use_case_2.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ def test_delete_certificate(self):
128128
name=certificate['name'],
129129
owner=self.user,
130130
common_name=domain,
131-
certificate=certificate['cert']
131+
certificate=certificate['cert'],
132+
key=certificate['key']
132133
)
133134
url = '/v2/certs/{}'.format(certificate['name'])
134135
response = self.client.delete(url)

rootfs/api/tests/test_certificate_use_case_3.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ def test_delete_certificate(self):
130130
name=certificate['name'],
131131
owner=self.user,
132132
common_name=domain,
133-
certificate=certificate['cert']
133+
certificate=certificate['cert'],
134+
key=certificate['key']
134135
)
135136
url = '/v2/certs/{}'.format(certificate['name'])
136137
response = self.client.delete(url)

rootfs/api/tests/test_certificate_use_case_4.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ def test_delete_certificate(self):
212212
name=certificate['name'],
213213
owner=self.user,
214214
common_name=domain,
215-
certificate=certificate['cert']
215+
certificate=certificate['cert'],
216+
key=certificate['key']
216217
)
217218
url = '/v2/certs/{}'.format(certificate['name'])
218219
response = self.client.delete(url)

rootfs/api/tests/test_certificate_use_case_5.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ def test_delete_certificate(self):
158158
name=certificate['name'],
159159
owner=self.user,
160160
common_name=domain,
161-
certificate=certificate['cert']
161+
certificate=certificate['cert'],
162+
key=certificate['key']
162163
)
163164

164165
# Remove certificate

0 commit comments

Comments
 (0)