diff --git a/docker/src/lemur.conf.py b/docker/src/lemur.conf.py
index 3cc51792..4cb3ae0c 100644
--- a/docker/src/lemur.conf.py
+++ b/docker/src/lemur.conf.py
@@ -1,4 +1,7 @@
import os
+import random
+import string
+import base64
from ast import literal_eval
_basedir = os.path.abspath(os.path.dirname(__file__))
@@ -6,10 +9,20 @@ _basedir = os.path.abspath(os.path.dirname(__file__))
CORS = os.environ.get("CORS") == "True"
debug = os.environ.get("DEBUG") == "True"
-SECRET_KEY = repr(os.environ.get('SECRET_KEY','Hrs8kCDNPuT9vtshsSWzlrYW+d+PrAXvg/HwbRE6M3vzSJTTrA/ZEw=='))
-LEMUR_TOKEN_SECRET = repr(os.environ.get('LEMUR_TOKEN_SECRET','YVKT6nNHnWRWk28Lra1OPxMvHTqg1ZXvAcO7bkVNSbrEuDQPABM0VQ=='))
-LEMUR_ENCRYPTION_KEYS = repr(os.environ.get('LEMUR_ENCRYPTION_KEYS','Ls-qg9j3EMFHyGB_NL0GcQLI6622n9pSyGM_Pu0GdCo='))
+def get_random_secret(length):
+ secret_key = ''.join(random.choice(string.ascii_uppercase) for x in range(round(length / 4)))
+ secret_key = secret_key + ''.join(random.choice("~!@#$%^&*()_+") for x in range(round(length / 4)))
+ secret_key = secret_key + ''.join(random.choice(string.ascii_lowercase) for x in range(round(length / 4)))
+ return secret_key + ''.join(random.choice(string.digits) for x in range(round(length / 4)))
+
+
+SECRET_KEY = repr(os.environ.get('SECRET_KEY', get_random_secret(32).encode('utf8')))
+
+LEMUR_TOKEN_SECRET = repr(os.environ.get('LEMUR_TOKEN_SECRET',
+ base64.b64encode(get_random_secret(32).encode('utf8'))))
+LEMUR_ENCRYPTION_KEYS = repr(os.environ.get('LEMUR_ENCRYPTION_KEYS',
+ base64.b64encode(get_random_secret(32).encode('utf8'))))
LEMUR_WHITELISTED_DOMAINS = []
diff --git a/lemur/authorities/schemas.py b/lemur/authorities/schemas.py
index ef6263a8..f80d1581 100644
--- a/lemur/authorities/schemas.py
+++ b/lemur/authorities/schemas.py
@@ -43,13 +43,13 @@ class AuthorityInputSchema(LemurInputSchema):
organization = fields.String(
missing=lambda: current_app.config.get("LEMUR_DEFAULT_ORGANIZATION")
)
- location = fields.String(
- missing=lambda: current_app.config.get("LEMUR_DEFAULT_LOCATION")
- )
+ location = fields.String()
country = fields.String(
missing=lambda: current_app.config.get("LEMUR_DEFAULT_COUNTRY")
)
state = fields.String(missing=lambda: current_app.config.get("LEMUR_DEFAULT_STATE"))
+ # Creating a String field instead of Email to allow empty value
+ email = fields.String()
plugin = fields.Nested(PluginInputSchema)
diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py
index f71d2199..60442de2 100644
--- a/lemur/certificates/models.py
+++ b/lemur/certificates/models.py
@@ -235,6 +235,7 @@ class Certificate(db.Model):
self.replaces = kwargs.get("replaces", [])
self.rotation = kwargs.get("rotation")
self.rotation_policy = kwargs.get("rotation_policy")
+ self.key_type = kwargs.get("key_type")
self.signing_algorithm = defaults.signing_algorithm(cert)
self.bits = defaults.bitstrength(cert)
self.external_id = kwargs.get("external_id")
diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py
index 56c91196..a360140e 100644
--- a/lemur/certificates/schemas.py
+++ b/lemur/certificates/schemas.py
@@ -107,9 +107,7 @@ class CertificateInputSchema(CertificateCreationSchema):
organization = fields.String(
missing=lambda: current_app.config.get("LEMUR_DEFAULT_ORGANIZATION")
)
- location = fields.String(
- missing=lambda: current_app.config.get("LEMUR_DEFAULT_LOCATION")
- )
+ location = fields.String()
country = fields.String(
missing=lambda: current_app.config.get("LEMUR_DEFAULT_COUNTRY")
)
@@ -155,6 +153,14 @@ class CertificateInputSchema(CertificateCreationSchema):
key_type = cert_utils.get_key_type_from_csr(data["csr"])
if key_type:
data["key_type"] = key_type
+
+ # This code will be exercised for certificate import (without CSR)
+ if data.get("key_type") is None:
+ if data.get("body"):
+ data["key_type"] = utils.get_key_type_from_certificate(data["body"])
+ else:
+ data["key_type"] = "RSA2048" # default value
+
return missing.convert_validity_years(data)
@@ -277,6 +283,7 @@ class CertificateOutputSchema(LemurOutputSchema):
serial = fields.String()
serial_hex = Hex(attribute="serial")
signing_algorithm = fields.String()
+ key_type = fields.String(allow_none=True)
status = fields.String()
user = fields.Nested(UserNestedOutputSchema)
@@ -317,6 +324,7 @@ class CertificateUploadInputSchema(CertificateCreationSchema):
body = fields.String(required=True)
chain = fields.String(missing=None, allow_none=True)
csr = fields.String(required=False, allow_none=True, validate=validators.csr)
+ key_type = fields.String()
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
@@ -364,6 +372,16 @@ class CertificateUploadInputSchema(CertificateCreationSchema):
# Throws ValidationError
validators.verify_cert_chain([cert] + chain)
+ @pre_load
+ def load_data(self, data):
+ if data.get("body"):
+ try:
+ data["key_type"] = utils.get_key_type_from_certificate(data["body"])
+ except ValueError:
+ raise ValidationError(
+ "Public certificate presented is not valid.", field_names=["body"]
+ )
+
class CertificateExportInputSchema(LemurInputSchema):
plugin = fields.Nested(PluginInputSchema)
diff --git a/lemur/dns_providers/schemas.py b/lemur/dns_providers/schemas.py
index 05b6471d..af9377b3 100644
--- a/lemur/dns_providers/schemas.py
+++ b/lemur/dns_providers/schemas.py
@@ -8,7 +8,7 @@ class DnsProvidersNestedOutputSchema(LemurOutputSchema):
__envelope__ = False
id = fields.Integer()
name = fields.String()
- providerType = fields.String()
+ provider_type = fields.String()
description = fields.String()
credentials = fields.String()
api_endpoint = fields.String()
diff --git a/lemur/migrations/versions/c301c59688d2_.py b/lemur/migrations/versions/c301c59688d2_.py
index 3b0a86f7..669c934f 100644
--- a/lemur/migrations/versions/c301c59688d2_.py
+++ b/lemur/migrations/versions/c301c59688d2_.py
@@ -90,7 +90,7 @@ def update_key_type():
# Loop through all certificates that are valid today or expired in the last 30 days.
for cert_id, body in conn.execute(
text(
- "select id, body from certificates where bits < 1024 and not_after > CURRENT_DATE - 31 and key_type is null")
+ "select id, body from certificates where not_after > CURRENT_DATE - 31 and key_type is null")
):
try:
cert_key_type = utils.get_key_type_from_certificate(body)
diff --git a/lemur/static/app/angular/authorities/authority/authority.js b/lemur/static/app/angular/authorities/authority/authority.js
index 9863bf4d..a449cff5 100644
--- a/lemur/static/app/angular/authorities/authority/authority.js
+++ b/lemur/static/app/angular/authorities/authority/authority.js
@@ -124,4 +124,8 @@ angular.module('lemur')
opened: false
};
+ $scope.populateSubjectEmail = function () {
+ $scope.authority.email = $scope.authority.owner;
+ };
+
});
diff --git a/lemur/static/app/angular/authorities/authority/distinguishedName.tpl.html b/lemur/static/app/angular/authorities/authority/distinguishedName.tpl.html
index c6a7d312..e94f856e 100644
--- a/lemur/static/app/angular/authorities/authority/distinguishedName.tpl.html
+++ b/lemur/static/app/angular/authorities/authority/distinguishedName.tpl.html
@@ -26,8 +26,7 @@
Location
+
diff --git a/lemur/static/app/angular/authorities/authority/tracking.tpl.html b/lemur/static/app/angular/authorities/authority/tracking.tpl.html
index 72d7e3d5..a561745f 100644
--- a/lemur/static/app/angular/authorities/authority/tracking.tpl.html
+++ b/lemur/static/app/angular/authorities/authority/tracking.tpl.html
@@ -21,7 +21,7 @@
diff --git a/lemur/static/app/angular/certificates/certificate/certificate.js b/lemur/static/app/angular/certificates/certificate/certificate.js
index 6b275328..9fadb655 100644
--- a/lemur/static/app/angular/certificates/certificate/certificate.js
+++ b/lemur/static/app/angular/certificates/certificate/certificate.js
@@ -251,10 +251,13 @@ angular.module('lemur')
$scope.certificate.csr = null; // should not clone CSR in case other settings are changed in clone
$scope.certificate.validityStart = null;
$scope.certificate.validityEnd = null;
- $scope.certificate.keyType = 'RSA2048'; // default algo to show during clone
$scope.certificate.description = 'Cloning from cert ID ' + editId;
$scope.certificate.replacedBy = []; // should not clone 'replaced by' info
$scope.certificate.removeReplaces(); // should not clone 'replacement cert' info
+
+ if(!$scope.certificate.keyType) {
+ $scope.certificate.keyType = 'RSA2048'; // default algo to select during clone if backend did not return algo
+ }
CertificateService.getDefaults($scope.certificate);
});
diff --git a/lemur/static/app/angular/certificates/certificate/distinguishedName.tpl.html b/lemur/static/app/angular/certificates/certificate/distinguishedName.tpl.html
index 72f168a0..bc08c786 100644
--- a/lemur/static/app/angular/certificates/certificate/distinguishedName.tpl.html
+++ b/lemur/static/app/angular/certificates/certificate/distinguishedName.tpl.html
@@ -38,9 +38,7 @@
Location
Key Length
{{ certificate.bits }}
+ Key Type
+ {{ certificate.keyType }}
Signing Algorithm
{{ certificate.signingAlgorithm }}
diff --git a/lemur/tests/conf.py b/lemur/tests/conf.py
index 0a288327..8255e674 100644
--- a/lemur/tests/conf.py
+++ b/lemur/tests/conf.py
@@ -1,5 +1,6 @@
# This is just Python which means you can inherit and tweak settings
+import base64
import os
import random
import string
@@ -9,8 +10,10 @@ _basedir = os.path.abspath(os.path.dirname(__file__))
# generate random secrets for unittest
def get_random_secret(length):
- input_ascii = string.ascii_letters + string.digits
- return ''.join(random.choice(input_ascii) for i in range(length))
+ secret_key = ''.join(random.choice(string.ascii_uppercase) for x in range(round(length / 4)))
+ secret_key = secret_key + ''.join(random.choice("~!@#$%^&*()_+") for x in range(round(length / 4)))
+ secret_key = secret_key + ''.join(random.choice(string.ascii_lowercase) for x in range(round(length / 4)))
+ return secret_key + ''.join(random.choice(string.digits) for x in range(round(length / 4)))
THREADS_PER_PAGE = 8
@@ -23,12 +26,14 @@ debug = False
TESTING = True
-# this is the secret key used by flask session management
-SECRET_KEY = "I/dVhOZNSMZMqrFJa5tWli6VQccOGudKerq3eWPMSzQNmHHVhMAQfQ=="
+# this is the secret key used by flask session management (utf8 encoded)
+SECRET_KEY = get_random_secret(length=32).encode('utf8')
-# You should consider storing these separately from your config
+
+# You should consider storing these separately from your config (should be URL-safe)
LEMUR_TOKEN_SECRET = "test"
-LEMUR_ENCRYPTION_KEYS = "o61sBLNBSGtAckngtNrfVNd8xy8Hp9LBGDstTbMbqCY="
+LEMUR_ENCRYPTION_KEYS = base64.urlsafe_b64encode(get_random_secret(length=32).encode('utf8'))
+
# List of domain regular expressions that non-admin users can issue
LEMUR_WHITELISTED_DOMAINS = [
@@ -61,7 +66,8 @@ LEMUR_ALLOW_WEEKEND_EXPIRATION = False
# Database
-# modify this if you are not using a local database
+# modify this if you are not using a local database. Do not use any development or production DBs,
+# as Unit Tests drop the whole schema, recreate and again drop everything at the end
SQLALCHEMY_DATABASE_URI = os.getenv(
"SQLALCHEMY_DATABASE_URI", "postgresql://lemur:lemur@localhost:5432/lemur"
)
diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py
index 41584cb3..a0a3b54e 100644
--- a/lemur/tests/test_certificates.py
+++ b/lemur/tests/test_certificates.py
@@ -154,7 +154,8 @@ def test_get_certificate_primitives(certificate):
with freeze_time(datetime.date(year=2016, month=10, day=30)):
primitives = get_certificate_primitives(certificate)
- assert len(primitives) == 26
+ assert len(primitives) == 25
+ assert (primitives["key_type"] == "RSA2048")
def test_certificate_output_schema(session, certificate, issuer_plugin):
@@ -253,17 +254,18 @@ def test_certificate_input_schema(client, authority):
"validityStart": arrow.get(2018, 11, 9).isoformat(),
"validityEnd": arrow.get(2019, 11, 9).isoformat(),
"dnsProvider": None,
+ "location": "A Place"
}
data, errors = CertificateInputSchema().load(input_data)
assert not errors
assert data["authority"].id == authority.id
+ assert data["location"] == "A Place"
# make sure the defaults got set
assert data["common_name"] == "test.example.com"
assert data["country"] == "US"
- assert data["location"] == "Los Gatos"
assert len(data.keys()) == 19
@@ -759,6 +761,7 @@ def test_reissue_certificate(
certificate.authority = crypto_authority
new_cert = reissue_certificate(certificate)
assert new_cert
+ assert (new_cert.key_type == "RSA2048")
def test_create_csr():
diff --git a/lemur/tests/test_pending_certificates.py b/lemur/tests/test_pending_certificates.py
index 3e755574..3718ef0a 100644
--- a/lemur/tests/test_pending_certificates.py
+++ b/lemur/tests/test_pending_certificates.py
@@ -55,6 +55,7 @@ def test_create_pending(pending_certificate, user, session):
assert real_cert.notify == pending_certificate.notify
assert real_cert.private_key == pending_certificate.private_key
assert real_cert.external_id == "54321"
+ assert real_cert.key_type == "RSA2048"
@pytest.mark.parametrize(
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 166722e8..c53c57f9 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -11,7 +11,7 @@ cffi==1.14.0 # via cryptography
cfgv==3.1.0 # via pre-commit
chardet==3.0.4 # via requests
colorama==0.4.3 # via twine
-cryptography==3.1 # via secretstorage
+cryptography==3.1.1 # via secretstorage
distlib==0.3.0 # via virtualenv
docutils==0.16 # via readme-renderer
filelock==3.0.12 # via virtualenv
diff --git a/requirements-docs.txt b/requirements-docs.txt
index 5c6fdf92..18338b5a 100644
--- a/requirements-docs.txt
+++ b/requirements-docs.txt
@@ -17,8 +17,8 @@ bcrypt==3.1.7 # via -r requirements.txt, flask-bcrypt, paramiko
beautifulsoup4==4.9.1 # via -r requirements.txt, cloudflare
billiard==3.6.3.0 # via -r requirements.txt, celery
blinker==1.4 # via -r requirements.txt, flask-mail, flask-principal, raven
-boto3==1.15.2 # via -r requirements.txt
-botocore==1.18.2 # via -r requirements.txt, boto3, s3transfer
+boto3==1.15.12 # via -r requirements.txt
+botocore==1.18.12 # via -r requirements.txt, boto3, s3transfer
celery[redis]==4.4.2 # via -r requirements.txt
certifi==2020.6.20 # via -r requirements.txt, requests
certsrv==2.1.1 # via -r requirements.txt
@@ -26,7 +26,7 @@ cffi==1.14.0 # via -r requirements.txt, bcrypt, cryptography, pynac
chardet==3.0.4 # via -r requirements.txt, requests
click==7.1.1 # via -r requirements.txt, flask
cloudflare==2.8.13 # via -r requirements.txt
-cryptography==3.1 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests
+cryptography==3.1.1 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests
dnspython3==1.15.0 # via -r requirements.txt
dnspython==1.15.0 # via -r requirements.txt, dnspython3
docutils==0.15.2 # via sphinx
diff --git a/requirements-tests.txt b/requirements-tests.txt
index b2b51cd7..2a81f432 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -10,21 +10,21 @@ aws-sam-translator==1.22.0 # via cfn-lint
aws-xray-sdk==2.5.0 # via moto
bandit==1.6.2 # via -r requirements-tests.in
black==20.8b1 # via -r requirements-tests.in
-boto3==1.15.2 # via aws-sam-translator, moto
+boto3==1.15.12 # via aws-sam-translator, moto
boto==2.49.0 # via moto
-botocore==1.18.2 # via aws-xray-sdk, boto3, moto, s3transfer
+botocore==1.18.12 # via aws-xray-sdk, boto3, moto, s3transfer
certifi==2020.6.20 # via requests
cffi==1.14.0 # via cryptography
cfn-lint==0.29.5 # via moto
chardet==3.0.4 # via requests
click==7.1.2 # via black, flask
coverage==5.3 # via -r requirements-tests.in
-cryptography==3.1 # via moto, python-jose, sshpubkeys
+cryptography==3.1.1 # via moto, python-jose, sshpubkeys
decorator==4.4.2 # via networkx
docker==4.2.0 # via moto
ecdsa==0.14.1 # via moto, python-jose, sshpubkeys
-factory-boy==3.0.1 # via -r requirements-tests.in
-faker==4.1.3 # via -r requirements-tests.in, factory-boy
+factory-boy==3.1.0 # via -r requirements-tests.in
+faker==4.4.0 # via -r requirements-tests.in, factory-boy
fakeredis==1.4.3 # via -r requirements-tests.in
flask==1.1.2 # via pytest-flask
freezegun==1.0.0 # via -r requirements-tests.in
@@ -44,7 +44,7 @@ jsonpointer==2.0 # via jsonpatch
jsonschema==3.2.0 # via aws-sam-translator, cfn-lint
markupsafe==1.1.1 # via jinja2, moto
mock==4.0.2 # via moto
-more-itertools==8.2.0 # via moto, pytest
+more-itertools==8.2.0 # via moto
moto==1.3.16 # via -r requirements-tests.in
mypy-extensions==0.4.3 # via black
networkx==2.4 # via cfn-lint
@@ -61,7 +61,7 @@ pyparsing==2.4.7 # via packaging
pyrsistent==0.16.0 # via jsonschema
pytest-flask==1.0.0 # via -r requirements-tests.in
pytest-mock==3.3.1 # via -r requirements-tests.in
-pytest==6.0.2 # via -r requirements-tests.in, pytest-flask, pytest-mock
+pytest==6.1.1 # via -r requirements-tests.in, pytest-flask, pytest-mock
python-dateutil==2.8.1 # via botocore, faker, freezegun, moto
python-jose[cryptography]==3.1.0 # via moto
pytz==2019.3 # via moto
diff --git a/requirements.txt b/requirements.txt
index cab2a8ed..83013eac 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,8 +15,8 @@ bcrypt==3.1.7 # via flask-bcrypt, paramiko
beautifulsoup4==4.9.1 # via cloudflare
billiard==3.6.3.0 # via celery
blinker==1.4 # via flask-mail, flask-principal, raven
-boto3==1.15.2 # via -r requirements.in
-botocore==1.18.2 # via -r requirements.in, boto3, s3transfer
+boto3==1.15.12 # via -r requirements.in
+botocore==1.18.12 # via -r requirements.in, boto3, s3transfer
celery[redis]==4.4.2 # via -r requirements.in
certifi==2020.6.20 # via -r requirements.in, requests
certsrv==2.1.1 # via -r requirements.in
@@ -24,7 +24,7 @@ cffi==1.14.0 # via bcrypt, cryptography, pynacl
chardet==3.0.4 # via requests
click==7.1.1 # via flask
cloudflare==2.8.13 # via -r requirements.in
-cryptography==3.1 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests
+cryptography==3.1.1 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests
dnspython3==1.15.0 # via -r requirements.in
dnspython==1.15.0 # via dnspython3
dyn==1.8.1 # via -r requirements.in