Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
slapos.toolbox
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Alain Takoudjou
slapos.toolbox
Commits
325666f9
Commit
325666f9
authored
Mar 28, 2017
by
Alain Takoudjou
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
certificate_authority: reimplement with new api
parent
3cdc9427
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
587 additions
and
295 deletions
+587
-295
slapos/certificate_authority/certificate_authority.py
slapos/certificate_authority/certificate_authority.py
+88
-41
slapos/certificate_authority/request.py
slapos/certificate_authority/request.py
+7
-4
slapos/certificate_authority/run.py
slapos/certificate_authority/run.py
+16
-4
slapos/certificate_authority/web/models.py
slapos/certificate_authority/web/models.py
+1
-16
slapos/certificate_authority/web/settings.py
slapos/certificate_authority/web/settings.py
+45
-32
slapos/certificate_authority/web/start_web.py
slapos/certificate_authority/web/start_web.py
+8
-1
slapos/certificate_authority/web/tools/certificate.py
slapos/certificate_authority/web/tools/certificate.py
+18
-41
slapos/certificate_authority/web/tools/error.py
slapos/certificate_authority/web/tools/error.py
+55
-0
slapos/certificate_authority/web/tools/tools.py
slapos/certificate_authority/web/tools/tools.py
+9
-0
slapos/certificate_authority/web/tools/users.py
slapos/certificate_authority/web/tools/users.py
+33
-0
slapos/certificate_authority/web/views.py
slapos/certificate_authority/web/views.py
+307
-156
No files found.
slapos/certificate_authority/certificate_authority.py
View file @
325666f9
...
@@ -198,6 +198,19 @@ class CertificateBase(object):
...
@@ -198,6 +198,19 @@ class CertificateBase(object):
else
:
else
:
return
False
return
False
def
checkCertificateValidity
(
self
,
ca_cert_file
,
cert_file
,
key_file
=
None
):
with
open
(
ca_cert_file
)
as
f_ca
:
ca_cert
=
f_ca
.
read
()
with
open
(
cert_file
)
as
f_cert
:
cert
=
f_cert
.
read
()
# XXX Considering only one trusted certificate here
if
not
self
.
verifyCertificateChain
(
cert
,
[
ca_cert
]):
return
False
if
key_file
:
return
self
.
validateCertAndKey
(
cert_file
,
key_file
)
return
True
def
generatePrivatekey
(
self
,
output_file
,
size
=
2048
):
def
generatePrivatekey
(
self
,
output_file
,
size
=
2048
):
key
=
crypto
.
PKey
()
key
=
crypto
.
PKey
()
key
.
generate_key
(
crypto
.
TYPE_RSA
,
size
)
key
.
generate_key
(
crypto
.
TYPE_RSA
,
size
)
...
@@ -215,7 +228,7 @@ class CertificateBase(object):
...
@@ -215,7 +228,7 @@ class CertificateBase(object):
def
generateCertificateRequest
(
self
,
key_file
,
output_file
,
cn
,
def
generateCertificateRequest
(
self
,
key_file
,
output_file
,
cn
,
country
,
state
,
locality
=
''
,
email
=
''
,
organization
=
''
,
country
,
state
,
locality
=
''
,
email
=
''
,
organization
=
''
,
organization_unit
=
''
,
digest
=
"sha
1
"
):
organization_unit
=
''
,
digest
=
"sha
256
"
):
with
open
(
key_file
)
as
fkey
:
with
open
(
key_file
)
as
fkey
:
key
=
crypto
.
load_privatekey
(
crypto
.
FILETYPE_PEM
,
fkey
.
read
())
key
=
crypto
.
load_privatekey
(
crypto
.
FILETYPE_PEM
,
fkey
.
read
())
...
@@ -238,20 +251,38 @@ class CertificateBase(object):
...
@@ -238,20 +251,38 @@ class CertificateBase(object):
os
.
chmod
(
output_file
,
0644
)
os
.
chmod
(
output_file
,
0644
)
def
checkCertificateValidity
(
self
,
ca_cert_file
,
cert_file
,
key_file
=
None
):
def
signData
(
self
,
key_file
,
data
,
digest
=
"sha256"
,
output_file
=
None
):
with
open
(
ca_cert_file
)
as
f_ca
:
"""
ca_cert
=
f_ca
.
read
()
Sign a data using digest and return signature. If output_file is provided
with
open
(
cert_file
)
as
f_cert
:
the signature will be written into the file.
cert
=
f_cert
.
read
()
"""
with
open
(
key_file
)
as
fkey
:
pkey
=
crypto
.
load_privatekey
(
crypto
.
FILETYPE_PEM
,
fkey
.
read
())
# XXX Considering only one trusted certificate here
sign
=
crypto
.
sign
(
pkey
,
data
,
digest
)
if
not
self
.
verifyCertificateChain
(
cert
,
[
ca_cert
]):
# data_base64 = base64.b64encode(sign)
return
False
if
output_file
is
None
:
return
False
return
sign
if
key_file
:
fd
=
os
.
open
(
output_file
,
return
self
.
validateCertAndKey
(
cert_file
,
key_file
)
os
.
O_CREAT
|
os
.
O_WRONLY
|
os
.
O_EXCL
|
os
.
O_TRUNC
,
return
True
0644
)
try
:
os
.
write
(
fd
,
sign
)
finally
:
os
.
close
(
fd
)
return
sign
def
verifyData
(
self
,
cert_string
,
signature
,
data
,
digest
=
"sha256"
):
"""
Verify the signature for a data string.
cert_string: is the certificate content as string
signature: is generate using 'signData' from the data to verify
data: content to verify
digest: by default is sha256, set the correct value
"""
x509
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
cert
)
return
crypto
.
verify
(
x509
,
signature
,
data
,
digest
)
def
readCertificateRequest
(
self
,
csr
):
def
readCertificateRequest
(
self
,
csr
):
req
=
crypto
.
load_certificate_request
(
crypto
.
FILETYPE_PEM
,
csr
)
req
=
crypto
.
load_certificate_request
(
crypto
.
FILETYPE_PEM
,
csr
)
...
@@ -415,6 +446,11 @@ class CertificateAuthorityRequest(CertificateBase):
...
@@ -415,6 +446,11 @@ class CertificateAuthorityRequest(CertificateBase):
self
.
ca_url
=
ca_url
self
.
ca_url
=
ca_url
self
.
logger
=
logger
self
.
logger
=
logger
self
.
max_retry
=
max_retry
self
.
max_retry
=
max_retry
self
.
X509Extension
=
X509Extension
()
while
self
.
ca_url
.
endswith
(
'/'
):
# remove all / at end or ca_url
self
.
ca_url
=
self
.
ca_url
[:
-
1
]
if
self
.
logger
is
None
:
if
self
.
logger
is
None
:
self
.
logger
=
logging
.
getLogger
(
'Certificate Request'
)
self
.
logger
=
logging
.
getLogger
(
'Certificate Request'
)
...
@@ -446,7 +482,7 @@ class CertificateAuthorityRequest(CertificateBase):
...
@@ -446,7 +482,7 @@ class CertificateAuthorityRequest(CertificateBase):
if
os
.
path
.
exists
(
self
.
cacertificate
)
and
os
.
stat
(
self
.
cacertificate
).
st_size
>
0
:
if
os
.
path
.
exists
(
self
.
cacertificate
)
and
os
.
stat
(
self
.
cacertificate
).
st_size
>
0
:
return
return
ca_cert_url
=
'%s/
ge
t/cacert.pem'
%
self
.
ca_url
ca_cert_url
=
'%s/
cr
t/cacert.pem'
%
self
.
ca_url
self
.
logger
.
info
(
"getting CA certificate file %s"
%
ca_cert_url
)
self
.
logger
.
info
(
"getting CA certificate file %s"
%
ca_cert_url
)
response
=
None
response
=
None
while
not
response
or
response
.
status_code
!=
200
:
while
not
response
or
response
.
status_code
!=
200
:
...
@@ -479,7 +515,7 @@ class CertificateAuthorityRequest(CertificateBase):
...
@@ -479,7 +515,7 @@ class CertificateAuthorityRequest(CertificateBase):
data
=
{
'csr'
:
csr
}
data
=
{
'csr'
:
csr
}
retry
=
0
retry
=
0
sleep_time
=
10
sleep_time
=
10
request_url
=
'%s/
request
'
%
self
.
ca_url
request_url
=
'%s/
csr
'
%
self
.
ca_url
# Save Cert in tmp to check later
# Save Cert in tmp to check later
cert_temp
=
'%s.tmp'
%
self
.
certificate
cert_temp
=
'%s.tmp'
%
self
.
certificate
csr_key_file
=
'%s.key'
%
csr_file
csr_key_file
=
'%s.key'
%
csr_file
...
@@ -491,31 +527,34 @@ class CertificateAuthorityRequest(CertificateBase):
...
@@ -491,31 +527,34 @@ class CertificateAuthorityRequest(CertificateBase):
csr_key
=
fkey
.
read
()
csr_key
=
fkey
.
read
()
if
csr_key
:
if
csr_key
:
self
.
logger
.
info
(
"Csr was already sent to CA,
key is
: %s"
%
csr_key
)
self
.
logger
.
info
(
"Csr was already sent to CA,
using csr
: %s"
%
csr_key
)
else
:
else
:
response
=
self
.
_request
(
'p
os
t'
,
request_url
,
data
=
data
)
response
=
self
.
_request
(
'p
u
t'
,
request_url
,
data
=
data
)
while
(
not
response
or
response
.
status_code
!=
20
0
)
and
retry
<
self
.
max_retry
:
while
(
not
response
or
response
.
status_code
!=
20
1
)
and
retry
<
self
.
max_retry
:
self
.
logger
.
error
(
"%s: Failed to sen
d
CSR.
\
n
%s"
%
(
self
.
logger
.
error
(
"%s: Failed to sen
t
CSR.
\
n
%s"
%
(
response
.
status_code
,
response
.
text
))
response
.
status_code
,
response
.
text
))
self
.
logger
.
info
(
"will retry in %s seconds..."
%
sleep_time
)
self
.
logger
.
info
(
"will retry in %s seconds..."
%
sleep_time
)
time
.
sleep
(
sleep_time
)
time
.
sleep
(
sleep_time
)
retry
+=
1
retry
+=
1
response
=
self
.
_request
(
'p
os
t'
,
request_url
,
data
=
data
)
response
=
self
.
_request
(
'p
u
t'
,
request_url
,
data
=
data
)
if
response
.
status_code
!=
20
0
:
if
response
.
status_code
!=
20
1
:
raise
Exception
(
"ERROR: failed to p
os
t CSR after % retry. Exiting..."
%
retry
)
raise
Exception
(
"ERROR: failed to p
u
t CSR after % retry. Exiting..."
%
retry
)
self
.
logger
.
info
(
"CSR succefully sent."
)
self
.
logger
.
info
(
"CSR succefully sent."
)
self
.
logger
.
debug
(
"Server reponse with csr key is %s"
%
response
.
text
)
# Get csr Location from request header: http://xxx.com/csr/key
csr_key
=
response
.
text
self
.
logger
.
debug
(
"Csr location is: %s"
%
response
.
headers
[
'Location'
])
csr_key
=
response
.
headers
[
'Location'
].
split
(
'/'
)[
-
1
]
with
open
(
csr_key_file
,
'w'
)
as
fkey
:
with
open
(
csr_key_file
,
'w'
)
as
fkey
:
fkey
.
write
(
response
.
text
)
fkey
.
write
(
response
.
text
)
# csr is xxx.csr.pem so cert is xxx.cert.pem
self
.
logger
.
info
(
"Waiting for signed certificate..."
)
self
.
logger
.
info
(
"Waiting for signed certificate..."
)
reply_url
=
'%s/
get/%s.cert.pem'
%
(
self
.
ca_url
,
csr_key
)
reply_url
=
'%s/
crt/%s.cert.pem'
%
(
self
.
ca_url
,
csr_key
[:
-
8
]
)
response
=
self
.
_request
(
'get'
,
reply_url
)
response
=
self
.
_request
(
'get'
,
reply_url
)
while
not
response
or
response
.
status_code
!=
200
:
while
not
response
or
response
.
status_code
!=
200
:
...
@@ -535,7 +574,7 @@ class CertificateAuthorityRequest(CertificateBase):
...
@@ -535,7 +574,7 @@ class CertificateAuthorityRequest(CertificateBase):
os
.
close
(
fd
)
os
.
close
(
fd
)
os
.
unlink
(
cert_temp
)
os
.
unlink
(
cert_temp
)
else
:
else
:
if
auto_revoke
:
"""
if auto_revoke:
self.logger.error("Certificate validation failed. "
\
self.logger.error("Certificate validation failed. "
\
"The signed certificate is going to be revoked...")
"The signed certificate is going to be revoked...")
self.revokeCertificateRequest(cert_temp,
self.revokeCertificateRequest(cert_temp,
...
@@ -547,38 +586,46 @@ class CertificateAuthorityRequest(CertificateBase):
...
@@ -547,38 +586,46 @@ class CertificateAuthorityRequest(CertificateBase):
except OSError, e:
except OSError, e:
if e.errno != errno.ENOENT:
if e.errno != errno.ENOENT:
# raise
# raise
pass
pass
"""
raise
Exception
(
"Error: Certificate validation failed. "
\
raise
Exception
(
"Error: Certificate validation failed. "
\
"This signed certificate should be revoked!"
)
"This signed certificate should be revoked!"
)
self
.
logger
.
info
(
"Certificate correctly saved at %s."
%
self
.
certificate
)
self
.
logger
.
info
(
"Certificate correctly saved at %s."
%
self
.
certificate
)
def
revokeCertificateRequest
(
self
,
cert_file
,
key_name
,
message
=
""
):
def
revokeCertificateRequest
(
self
,
cert_file
,
message
=
""
):
"""
"""
Send a revocation request for the givent certificate to the master.
Send a revocation request for the givent certificate to the master.
"""
"""
sleep_time
=
10
sleep_time
=
10
retry
=
0
retry
=
0
cert
=
self
.
freadX509
(
cert_file
)
serial
=
'{0:x}'
.
format
(
int
(
cert
.
get_serial_number
()))
request_url
=
'%s/requestrevoke'
%
self
.
ca_url
data
=
{
'serial'
:
serial
,
'name'
:
key_name
,
'reason'
:
message
}
self
.
logger
.
info
(
"Sent Certificate revocation request for %s, serial=%s."
%
(
with
open
(
cert_file
)
as
f
:
key_name
,
serial
))
cert_string
=
f
.
read
()
cert
=
self
.
readX509
(
cert_string
)
digest
=
"sha256"
payload
=
json
.
dumps
(
dict
(
reason
=
message
,
cert
=
cert_string
))
signature
=
self
.
signData
(
self
.
key
,
payload
,
digest
)
request_url
=
'%s/crt/revoke'
%
self
.
ca_url
data
=
{
'digest'
:
digest
,
'payload'
:
payload
,
'signature'
:
signature
}
self
.
logger
.
info
(
"Sent Certificate revocation request for CN: %s."
%
(
cert
.
get_subject
().
CN
))
response
=
self
.
_request
(
'p
os
t'
,
request_url
,
data
=
data
)
response
=
self
.
_request
(
'p
u
t'
,
request_url
,
data
=
data
)
while
(
not
response
or
response
.
status_code
!=
20
0
)
and
retry
<
self
.
max_retry
:
while
(
not
response
or
response
.
status_code
!=
20
1
)
and
retry
<
self
.
max_retry
:
self
.
logger
.
error
(
"%s: Failed to send Rovocation request.
\
n
%s"
%
(
self
.
logger
.
error
(
"%s: Failed to send Rovocation request.
\
n
%s"
%
(
response
.
status_code
,
response
.
text
))
response
.
status_code
,
response
.
text
))
self
.
logger
.
info
(
"will retry in %s seconds..."
%
sleep_time
)
self
.
logger
.
info
(
"will retry in %s seconds..."
%
sleep_time
)
time
.
sleep
(
sleep_time
)
time
.
sleep
(
sleep_time
)
retry
+=
1
retry
+=
1
response
=
self
.
_request
(
'p
os
t'
,
request_url
,
data
=
data
)
response
=
self
.
_request
(
'p
u
t'
,
request_url
,
data
=
data
)
if
response
.
status_code
!=
20
0
:
if
response
.
status_code
!=
20
1
:
raise
Exception
(
"ERROR: failed to post revoke request after %s retry. Exiting..."
%
retry
)
raise
Exception
(
"ERROR: failed to post revoke request after %s retry. Exiting..."
%
retry
)
self
.
logger
.
info
(
"Certificate revocation request for %s
.cert.pem
successfully sent."
%
(
self
.
logger
.
info
(
"Certificate revocation request for %s successfully sent."
%
(
key_nam
e
))
cert_fil
e
))
slapos/certificate_authority/request.py
View file @
325666f9
...
@@ -50,9 +50,12 @@ def parseArguments():
...
@@ -50,9 +50,12 @@ def parseArguments():
parser
.
add_argument
(
'--organization_unit'
,
parser
.
add_argument
(
'--organization_unit'
,
default
=
'Company Unit'
,
default
=
'Company Unit'
,
help
=
'The Organisation Unit Name'
)
help
=
'The Organisation Unit Name'
)
parser
.
add_argument
(
'--auto_revoke'
,
default
=
True
,
action
=
"store_true"
,
parser
.
add_argument
(
'--revoke'
,
help
=
'Request Revoke Certificate if validation fail'
)
default
=
False
,
action
=
"store_true"
,
help
=
'Revoke the current certificate'
)
parser
.
add_argument
(
'--revoke_reason'
,
help
=
'Say why the certificat should be revoked'
)
return
parser
return
parser
...
@@ -84,7 +87,7 @@ def requestCertificateWeb():
...
@@ -84,7 +87,7 @@ def requestCertificateWeb():
cn
=
config
.
cn
,
country
=
config
.
country
,
state
=
config
.
state
,
cn
=
config
.
cn
,
country
=
config
.
country
,
state
=
config
.
state
,
locality
=
config
.
locality
,
email
=
config
.
email
,
locality
=
config
.
locality
,
email
=
config
.
email
,
organization
=
config
.
organization
,
organization
=
config
.
organization
,
organization_unit
=
config
.
organization_unit
,
digest
=
"sha
1
"
)
organization_unit
=
config
.
organization_unit
,
digest
=
"sha
256
"
)
ca
.
signCertificateWeb
(
config
.
csr_file
,
auto_revoke
=
config
.
auto_revoke
)
ca
.
signCertificateWeb
(
config
.
csr_file
,
auto_revoke
=
config
.
auto_revoke
)
slapos/certificate_authority/run.py
View file @
325666f9
...
@@ -13,7 +13,6 @@ import traceback
...
@@ -13,7 +13,6 @@ import traceback
from
flask_user
import
UserManager
,
SQLAlchemyAdapter
from
flask_user
import
UserManager
,
SQLAlchemyAdapter
from
flask_mail
import
Mail
from
flask_mail
import
Mail
from
slapos.certificate_authority.web.views
import
app
from
slapos.certificate_authority.web.views
import
app
from
slapos.certificate_authority.web.start_web
import
app
,
db
,
init_app
from
slapos.certificate_authority.certificate_authority
import
CertificateAuthority
from
slapos.certificate_authority.certificate_authority
import
CertificateAuthority
def
parseArguments
():
def
parseArguments
():
...
@@ -53,6 +52,9 @@ def parseArguments():
...
@@ -53,6 +52,9 @@ def parseArguments():
help
=
'Path for log output'
)
help
=
'Path for log output'
)
parser
.
add_argument
(
'--db_file'
,
parser
.
add_argument
(
'--db_file'
,
help
=
'Path of file to use to store User Account information. Default: $ca_dir/ca.db'
)
help
=
'Path of file to use to store User Account information. Default: $ca_dir/ca.db'
)
#parser.add_argument('--external_url',
# default='',
# help='The HTTP URL used to connect to CA server')
parser
.
add_argument
(
'--trusted_host'
,
parser
.
add_argument
(
'--trusted_host'
,
default
=
[],
default
=
[],
action
=
'append'
,
dest
=
'trusted_host_list'
,
action
=
'append'
,
dest
=
'trusted_host_list'
,
...
@@ -89,13 +91,16 @@ def start():
...
@@ -89,13 +91,16 @@ def start():
"""
"""
start certificate authority service
start certificate authority service
"""
"""
flask
.
config
.
Config
.
__getattr__
=
getConfig
options
=
parseArguments
()
options
=
parseArguments
()
if
not
options
.
ca_dir
:
if
not
options
.
ca_dir
:
options
.
ca_dir
=
os
.
getcwd
()
options
.
ca_dir
=
os
.
getcwd
()
else
:
else
:
options
.
ca_dir
=
os
.
path
.
abspath
(
options
.
ca_dir
)
options
.
ca_dir
=
os
.
path
.
abspath
(
options
.
ca_dir
)
os
.
environ
[
'CA_INSTANCE_PATH'
]
=
options
.
ca_dir
from
slapos.certificate_authority.web.start_web
import
app
,
db
,
init_app
flask
.
config
.
Config
.
__getattr__
=
getConfig
if
not
options
.
config_file
:
if
not
options
.
config_file
:
options
.
config_file
=
os
.
path
.
join
(
options
.
ca_dir
,
'openssl.cnf'
)
options
.
config_file
=
os
.
path
.
join
(
options
.
ca_dir
,
'openssl.cnf'
)
if
not
options
.
db_file
:
if
not
options
.
db_file
:
...
@@ -116,11 +121,15 @@ def start():
...
@@ -116,11 +121,15 @@ def start():
os
.
chdir
(
options
.
ca_dir
)
os
.
chdir
(
options
.
ca_dir
)
logger
=
getLogger
(
options
.
debug
,
options
.
log_file
)
logger
=
getLogger
(
options
.
debug
,
options
.
log_file
)
app
.
logger
.
addHandler
(
logger
)
ca
=
CertificateAuthority
(
options
.
openssl_bin
,
ca
=
CertificateAuthority
(
options
.
openssl_bin
,
openssl_configuration
=
options
.
config_file
,
certificate
=
options
.
cert_file
,
openssl_configuration
=
options
.
config_file
,
certificate
=
options
.
cert_file
,
key
=
options
.
key_file
,
crl
=
options
.
crl_file
,
ca_directory
=
options
.
ca_dir
)
key
=
options
.
key_file
,
crl
=
options
.
crl_file
,
ca_directory
=
options
.
ca_dir
)
app
.
config
.
from_object
(
'slapos.certificate_authority.web.settings'
)
if
options
.
debug
:
app
.
config
.
from_object
(
'slapos.certificate_authority.web.settings.Development'
)
else
:
app
.
config
.
from_object
(
'slapos.certificate_authority.web.settings.Production'
)
app
.
config
.
update
(
app
.
config
.
update
(
ca_dir
=
options
.
ca_dir
,
ca_dir
=
options
.
ca_dir
,
trusted_host_list
=
options
.
trusted_host_list
,
trusted_host_list
=
options
.
trusted_host_list
,
...
@@ -134,8 +143,12 @@ def start():
...
@@ -134,8 +143,12 @@ def start():
SQLALCHEMY_DATABASE_URI
=
'sqlite:///%s'
%
options
.
db_file
,
SQLALCHEMY_DATABASE_URI
=
'sqlite:///%s'
%
options
.
db_file
,
ca
=
ca
,
ca
=
ca
,
log_file
=
options
.
log_file
,
log_file
=
options
.
log_file
,
# base_url=(options.external_url or 'http://%s:%s' % (options.host, options.port))
)
)
if
os
.
path
.
exists
(
os
.
path
.
join
(
options
.
ca_dir
,
'local.setting.py'
)):
app
.
config
.
from_pyfile
(
'local.setting.py'
,
silent
=
True
)
for
key
in
[
'csr'
,
'req'
,
'cert'
,
'crl'
,
'key'
,
'newcert'
]:
for
key
in
[
'csr'
,
'req'
,
'cert'
,
'crl'
,
'key'
,
'newcert'
]:
try
:
try
:
path
=
app
.
config
[
'%s_dir'
%
key
]
path
=
app
.
config
[
'%s_dir'
%
key
]
...
@@ -154,7 +167,6 @@ def start():
...
@@ -154,7 +167,6 @@ def start():
# Initialize Flask extensions
# Initialize Flask extensions
init_app
()
init_app
()
app
.
logger
.
addHandler
(
logger
)
app
.
logger
.
info
(
"Certificate Authority server started on http://%s:%s"
%
(
app
.
logger
.
info
(
"Certificate Authority server started on http://%s:%s"
%
(
options
.
host
,
options
.
port
))
options
.
host
,
options
.
port
))
app
.
run
(
app
.
run
(
...
...
slapos/certificate_authority/web/models.py
View file @
325666f9
...
@@ -49,22 +49,7 @@ class Certificate(db.Model):
...
@@ -49,22 +49,7 @@ class Certificate(db.Model):
def
__repr__
(
self
):
def
__repr__
(
self
):
return
'<CertificateMap %r>'
%
(
self
.
serial
)
return
'<CertificateMap %r>'
%
(
self
.
serial
)
class
Revoke
(
db
.
Model
):
class
Revocation
(
db
.
Model
):
"""
This table contains information about certificate revocation
"""
__tablename__
=
'revoke'
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
comment
=
db
.
Column
(
db
.
Text
())
serial
=
db
.
Column
(
db
.
String
(
50
),
unique
=
True
)
revoke_date
=
db
.
Column
(
db
.
DateTime
)
# link to revoke request if was requested by users
revoke_request_id
=
db
.
Column
(
db
.
Integer
,
server_default
=
''
)
def
__repr__
(
self
):
return
'<CertificateMap %r>'
%
(
self
.
serial
)
class
RevokeRequest
(
db
.
Model
):
"""
"""
This table store certificate revocation request from users
This table store certificate revocation request from users
"""
"""
...
...
slapos/certificate_authority/web/settings.py
View file @
325666f9
import
os
import
os
# Application settings
APP_NAME
=
"Certificate Authority web app"
class
BaseConfig
(
object
):
# DO NOT use "DEBUG = True" in production environments
# Application settings
DEBUG
=
True
APP_NAME
=
"Certificate Authority web app"
# DO NOT use Unsecure Secrets in production environments
# DO NOT use Unsecure Secrets in production environments
# Generate a safe one with:
# Generate a safe one with:
# python -c "import os; print repr(os.urandom(24));"
# python -c "import os; print repr(os.urandom(24));"
SECRET_KEY
=
'This is an UNSECURE Secret. CHANGE THIS for production environments.'
SECRET_KEY
=
'This is an UNSECURE Secret. CHANGE THIS for production environments.'
# SQLAlchemy settings
# SQLAlchemy settings
SQLALCHEMY_DATABASE_URI
=
'sqlite:///ca.db'
SQLALCHEMY_DATABASE_URI
=
'sqlite:///ca.db'
SQLALCHEMY_TRACK_MODIFICATIONS
=
False
SQLALCHEMY_TRACK_MODIFICATIONS
=
False
CSRF_ENABLED
=
True
CSRF_ENABLED
=
True
# Flask-Mail settings
# Flask-Mail settings
# For smtp.gmail.com to work, you MUST set "Allow less secure apps" to ON in Google Accounts.
# For smtp.gmail.com to work, you MUST set "Allow less secure apps" to ON in Google Accounts.
# Change it in https://myaccount.google.com/security#connectedapps (near the bottom).
# Change it in https://myaccount.google.com/security#connectedapps (near the bottom).
MAIL_SERVER
=
'smtp.gmail.com'
MAIL_SERVER
=
'smtp.gmail.com'
MAIL_PORT
=
587
MAIL_PORT
=
587
MAIL_USE_SSL
=
False
MAIL_USE_SSL
=
False
MAIL_USE_TLS
=
True
MAIL_USE_TLS
=
True
MAIL_USERNAME
=
'yourname@gmail.com'
MAIL_USERNAME
=
'yourname@gmail.com'
MAIL_PASSWORD
=
'password'
MAIL_PASSWORD
=
'password'
MAIL_DEFAULT_SENDER
=
'"Your Name" <yourname@gmail.com>'
MAIL_DEFAULT_SENDER
=
'"Your Name" <yourname@gmail.com>'
# Used by email templates
# Used by email templates
USER_APP_NAME
=
"Certificate Authority"
USER_APP_NAME
=
"Certificate Authority"
# Internal application
# Internal view application
USER_AFTER_LOGIN_ENDPOINT
=
''
USER_AFTER_LOGIN_ENDPOINT
=
''
USER_AFTER_LOGOUT_ENDPOINT
=
''
USER_AFTER_LOGOUT_ENDPOINT
=
''
USER_ENABLE_USERNAME
=
True
USER_ENABLE_USERNAME
=
True
USER_ENABLE_EMAIL
=
False
USER_ENABLE_EMAIL
=
False
USER_ENABLE_REGISTRATION
=
False
USER_ENABLE_REGISTRATION
=
False
USER_ENABLE_CHANGE_USERNAME
=
False
USER_ENABLE_CHANGE_USERNAME
=
False
\ No newline at end of file
# Allowed digest for signature
CA_DIGEST_LIST
=
[
'sha256'
,
'sha384'
,
'sha512'
]
class
Development
(
BaseConfig
):
DEBUG
=
True
TESTING
=
True
class
Production
(
BaseConfig
):
# DO NOT use "DEBUG = True" in production environments
DEBUG
=
False
TESTING
=
False
\ No newline at end of file
slapos/certificate_authority/web/start_web.py
View file @
325666f9
...
@@ -2,9 +2,16 @@ from flask_sqlalchemy import SQLAlchemy
...
@@ -2,9 +2,16 @@ from flask_sqlalchemy import SQLAlchemy
from
flask_user
import
UserManager
,
SQLAlchemyAdapter
from
flask_user
import
UserManager
,
SQLAlchemyAdapter
from
flask
import
Flask
from
flask
import
Flask
from
flask_mail
import
Mail
from
flask_mail
import
Mail
from
slapos.certificate_authority.web.settings
import
BaseConfig
import
os
import
os
app
=
Flask
(
__name__
)
# CA_INSTANCE_PATH is the base directory of application, send to environ
app
=
Flask
(
__name__
,
instance_path
=
config_name
=
os
.
getenv
(
'CA_INSTANCE_PATH'
,
os
.
getcwd
()),
instance_relative_config
=
True
)
# Use default value so SQLALCHEMY will not warn because there is not db_uri
app
.
config
[
'SQLALCHEMY_DATABASE_URI'
]
=
BaseConfig
.
SQLALCHEMY_DATABASE_URI
db
=
SQLAlchemy
(
app
)
db
=
SQLAlchemy
(
app
)
def
init_app
():
def
init_app
():
...
...
slapos/certificate_authority/web/tools.py
→
slapos/certificate_authority/web/tools
/certificate
.py
View file @
325666f9
...
@@ -5,45 +5,20 @@ import time
...
@@ -5,45 +5,20 @@ import time
import
urllib
import
urllib
from
datetime
import
datetime
from
datetime
import
datetime
from
slapos.certificate_authority.web.start_web
import
app
,
db
from
slapos.certificate_authority.web.start_web
import
app
,
db
from
slapos.certificate_authority.web.models
import
(
User
,
Certificate
,
from
slapos.certificate_authority.web.models
import
(
Certificate
,
RevokeRequest
,
Revoke
,
CERT_STATUS_VALIDATED
,
CERT_STATUS_REVOKED
,
RevokeRequest
,
Revoke
,
CERT_STATUS_VALIDATED
,
CERT_STATUS_REVOKED
,
CERT_STATUS_PENDING
,
CERT_STATUS_REJECTED
)
CERT_STATUS_PENDING
,
CERT_STATUS_REJECTED
)
from
slapos.certificate_authority.certificate_authority
import
CertificateBase
def
find_or_create_user
(
first_name
,
last_name
,
email
,
username
,
password
):
class
CertificateTools
:
""" Find existing user or create new user """
user
=
User
.
query
.
filter
(
User
.
username
==
username
).
first
()
def
signCertificate
(
self
,
req_file
,
cert_id
):
if
not
user
:
user
=
User
(
email
=
email
,
first_name
=
first_name
,
last_name
=
last_name
,
username
=
username
,
password
=
app
.
user_manager
.
hash_password
(
password
),
active
=
True
,
confirmed_at
=
datetime
.
utcnow
()
)
db
.
session
.
add
(
user
)
db
.
session
.
commit
()
return
user
def
find_user
(
username
):
return
User
.
query
.
filter
(
User
.
username
==
username
).
first
()
def
get_string_num
(
number
):
if
number
<
10
:
return
'0%s'
%
number
return
str
(
number
)
class
CertificateTools
(
object
):
def
signCertificate
(
self
,
cert_id
,
req_file
):
"""
"""
Sign a certificate, cert_id is the name used by the user to download the cert
Sign a certificate, cert_id is the name used by the user to download the cert
"""
"""
csr_dest
=
os
.
path
.
join
(
app
.
config
.
csr_dir
,
'%s.csr.pem'
%
cert_id
)
csr_dest
=
os
.
path
.
join
(
app
.
config
.
csr_dir
,
'%s.csr.pem'
%
cert_id
)
cert_name
=
'%s.cert.pem'
%
cert_id
try
:
try
:
# Avoid signing two certificate at the same time (for unique serial)
# Avoid signing two certificate at the same time (for unique serial)
app
.
config
.
ca
.
_lock
()
app
.
config
.
ca
.
_lock
()
...
@@ -58,7 +33,7 @@ class CertificateTools(object):
...
@@ -58,7 +33,7 @@ class CertificateTools(object):
app
.
config
.
ca
.
signCertificateRequest
(
req_file
,
output
)
app
.
config
.
ca
.
signCertificateRequest
(
req_file
,
output
)
cert
=
app
.
config
.
ca
.
freadX509
(
output
)
cert
=
app
.
config
.
ca
.
freadX509
(
output
)
cert_db
=
Certificate
(
cert_db
=
Certificate
(
name
=
'%s.cert.pem'
%
cert_id
,
name
=
cert_name
,
serial
=
next_serial
,
serial
=
next_serial
,
filename
=
'%s.cert.pem'
%
next_serial
,
filename
=
'%s.cert.pem'
%
next_serial
,
common_name
=
cert
.
get_subject
().
CN
,
common_name
=
cert
.
get_subject
().
CN
,
...
@@ -89,17 +64,17 @@ class CertificateTools(object):
...
@@ -89,17 +64,17 @@ class CertificateTools(object):
finally
:
finally
:
app
.
config
.
ca
.
_unlock
()
app
.
config
.
ca
.
_unlock
()
def
addRevokeRequest
(
self
,
serial
,
hash_name
,
message
):
return
cert_name
cert_path
=
os
.
path
.
join
(
app
.
config
.
cert_dir
,
'%s.cert.pem'
%
serial
)
if
not
os
.
path
.
exists
(
cert_path
):
def
addRevokeRequest
(
self
,
cert
,
message
):
# This check is fast but 'serial'.cert.pem should the the cert filename in db
x509
=
app
.
config
.
ca
.
readX509
(
cert
)
return
False
serial
=
self
.
getSerialToInt
(
x509
)
cert
=
Certificate
.
query
.
filter
(
cert
=
Certificate
.
query
.
filter
(
Certificate
.
status
==
CERT_STATUS_VALIDATED
Certificate
.
status
==
CERT_STATUS_VALIDATED
).
filter
(
Certificate
.
serial
==
serial
).
first
()
).
filter
(
Certificate
.
serial
==
get_string_num
(
serial
)
).
first
()
if
not
cert
or
cert
.
name
!=
'%s.cert.pem'
%
hash_name
:
if
not
cert
or
cert
.
name
!=
'%s.cert.pem'
%
hash_name
:
# This certificate not found or not match
# This certificate not found or not match
or was revoked
return
False
return
False
# Create Request
# Create Request
...
@@ -161,8 +136,8 @@ class CertificateTools(object):
...
@@ -161,8 +136,8 @@ class CertificateTools(object):
return
""
return
""
def
getCertificateList
(
self
,
with_cacerts
=
True
):
def
getCertificateList
(
self
,
with_cacerts
=
True
):
ca_cert
=
app
.
config
.
ca
.
freadX509
(
app
.
config
.
ca
.
certificate
)
if
with_cacerts
:
if
with_cacerts
:
ca_cert
=
app
.
config
.
ca
.
freadX509
(
app
.
config
.
ca
.
certificate
)
data_list
=
[
data_list
=
[
{
{
'index'
:
1
,
'index'
:
1
,
...
@@ -170,6 +145,7 @@ class CertificateTools(object):
...
@@ -170,6 +145,7 @@ class CertificateTools(object):
'name'
:
os
.
path
.
basename
(
app
.
config
.
ca
.
certificate
),
'name'
:
os
.
path
.
basename
(
app
.
config
.
ca
.
certificate
),
'cn'
:
ca_cert
.
get_subject
().
CN
,
'cn'
:
ca_cert
.
get_subject
().
CN
,
'expiration_date'
:
datetime
.
strptime
(
ca_cert
.
get_notAfter
(),
"%Y%m%d%H%M%SZ"
),
'expiration_date'
:
datetime
.
strptime
(
ca_cert
.
get_notAfter
(),
"%Y%m%d%H%M%SZ"
),
'start_date'
:
datetime
.
strptime
(
ca_cert
.
get_notBefore
(),
"%Y%m%d%H%M%SZ"
)
},
},
{
{
'index'
:
2
,
'index'
:
2
,
...
@@ -177,6 +153,7 @@ class CertificateTools(object):
...
@@ -177,6 +153,7 @@ class CertificateTools(object):
'name'
:
os
.
path
.
basename
(
app
.
config
.
ca
.
ca_crl
),
'name'
:
os
.
path
.
basename
(
app
.
config
.
ca
.
ca_crl
),
'cn'
:
"Certificate Revocation List"
,
'cn'
:
"Certificate Revocation List"
,
'expiration_date'
:
'---'
,
'expiration_date'
:
'---'
,
'start_date'
:
'---'
,
}
}
]
]
index
=
3
index
=
3
...
...
slapos/certificate_authority/web/tools/error.py
0 → 100644
View file @
325666f9
import
os
class
Error
():
MISSING_PARAM
=
{
code
:
1
,
name
:
"MissingParameter"
,
message
:
"Parameter(s) required is missing or empty."
}
CSR_FORMAT
=
{
code
:
2
,
name
:
"FileFormat"
,
message
:
"Not a valid PEM certificate signing request"
}
CSR_INVALID_CN
=
{
code
:
3
,
name
:
"CertificateSigningRequestContent"
,
message
:
"Request does not contain a Common Name"
}
CERT_FORMAT
=
{
code
:
4
,
name
:
"FileFormat"
,
message
:
"Not a valid PEM certificate"
}
SIGNATURE_VERIFICATION
=
{
code
:
5
,
name
:
"SignatureMismatch"
,
message
:
"Signature verification failed. Request was not signed with the correct key"
}
BAD_SIGNATURE_DIGEST
=
{
code
:
6
,
name
:
"SignatureMismatch"
,
message
:
"Hash algorithm not supported"
}
CSR_CONTENT_MISMATCH
=
{
code
:
7
,
name
:
"CertificateSigningRequestContent"
,
message
:
"Request content does not match replaced certificate"
}
JSON_FORMAT
=
{
code
:
8
,
name
:
"JsonFormat"
,
message
:
"Not a valid Json content submitted"
}
PAYLOAD_CONTENT
=
{
code
:
9
,
name
:
"PayloadContentInvalid"
,
message
:
"Submitted payload parameter is not valid"
}
INVALID_DIGEST
=
{
code
:
4
,
name
:
"IvalidORNotAllowedDigest"
,
message
:
"The Digest submitted is not accepted by CA or is invalid"
}
slapos/certificate_authority/web/tools/tools.py
0 → 100644
View file @
325666f9
# -*- coding: utf-8 -*-
import
os
def
get_string_num
(
number
):
if
number
<
10
:
return
'0%s'
%
number
return
str
(
number
)
\ No newline at end of file
slapos/certificate_authority/web/tools/users.py
0 → 100644
View file @
325666f9
# -*- coding: utf-8 -*-
import
os
from
datetime
import
datetime
from
slapos.certificate_authority.web.start_web
import
app
,
db
from
slapos.certificate_authority.web.models
import
User
def
check_authentication
(
username
,
password
):
user
=
self
.
find_user
(
username
):
if
user
:
return
app
.
user_manager
.
hash_password
(
password
)
==
user
.
password
else
:
return
False
def
find_or_create_user
(
first_name
,
last_name
,
email
,
username
,
password
):
""" Find existing user or create new user """
user
=
User
.
query
.
filter
(
User
.
username
==
username
).
first
()
if
not
user
:
user
=
User
(
email
=
email
,
first_name
=
first_name
,
last_name
=
last_name
,
username
=
username
,
password
=
app
.
user_manager
.
hash_password
(
password
),
active
=
True
,
confirmed_at
=
datetime
.
utcnow
()
)
db
.
session
.
add
(
user
)
db
.
session
.
commit
()
return
user
def
find_user
(
username
):
return
User
.
query
.
filter
(
User
.
username
==
username
).
first
()
\ No newline at end of file
slapos/certificate_authority/web/views.py
View file @
325666f9
...
@@ -8,50 +8,107 @@ from OpenSSL import crypto
...
@@ -8,50 +8,107 @@ from OpenSSL import crypto
import
uuid
import
uuid
from
datetime
import
datetime
from
datetime
import
datetime
from
flask
import
(
Flask
,
session
,
request
,
redirect
,
url_for
,
render_template
,
from
flask
import
(
Flask
,
session
,
request
,
redirect
,
url_for
,
render_template
,
jsonify
,
session
,
abort
,
send_file
,
flash
,
g
)
jsonify
,
session
,
abort
,
send_file
,
flash
,
g
,
Response
)
from
slapos.certificate_authority.web.start_web
import
app
,
db
from
slapos.certificate_authority.web.start_web
import
app
,
db
from
slapos.certificate_authority.web.tools
import
(
find_user
,
find_or_create_user
,
from
slapos.certificate_authority.web.tools.users
import
find_user
,
find_or_create_user
get_string_num
,
CertificateTools
)
from
slapos
.
certificate_authority
.
web
.
tools
.
tools
get_string_num
from
slapos.certificate_authority.web.tools.error
import
Error
as
error
from
slapos.certificate_authority.web.tools.certificate
import
CertificateTools
from
flask_user
import
login_required
,
current_user
from
flask_user
import
login_required
,
current_user
from
flask_login
import
logout_user
#, login_user, current_user, login_required
from
flask_login
import
logout_user
#, login_user, current_user, login_required
from
slapos.certificate_authority.web.models
import
(
UserProfileForm
,
Certificate
)
from
slapos.certificate_authority.web.models
import
(
UserProfileForm
,
Certificate
)
from
slapos.runner.utils
import
tail
from
slapos.runner.utils
import
tail
from
functools
import
wraps
cert_tools
=
CertificateTools
()
cert_tools
=
CertificateTools
()
def
authenticated_method
(
func
):
""" This decorator ensures that the current user is logged in before calling the actual view.
Abort with 401 when the user is not logged in."""
@
wraps
(
func
)
def
decorated_view
(
*
args
,
**
kwargs
):
# User must be authenticated
auth
=
request
.
authorization
if
not
auth
:
return
abort
(
401
)
elif
not
Users
.
check_authentication
(
auth
.
username
,
auth
.
password
):
return
abort
(
401
)
# Call the actual view
return
func
(
*
args
,
**
kwargs
)
return
decorated_view
@
app
.
errorhandler
(
401
)
def
error401
(
error
):
if
error
.
description
is
None
:
message
=
{
'code'
:
401
,
'name'
:
'Unauthorized'
,
'message'
:
"Authenticate."
}
else
:
message
=
error
.
description
response
=
jsonify
(
message
)
response
.
status_code
=
401
response
.
headers
[
'WWW-Authenticate'
]
=
'Basic realm="Login Required"'
return
response
@
app
.
errorhandler
(
403
)
@
app
.
errorhandler
(
403
)
def
error403
(
res
):
def
error403
(
error
):
return
"403: Forbidden. Your are not allowed to access %s"
%
res
,
403
if
error
.
description
is
None
:
message
=
{
'code'
:
404
,
'name'
:
'Forbidden'
,
'message'
:
'Forbidden. Your are not allowed to access %s'
%
request
.
url
,
}
else
:
message
=
error
.
description
response
=
jsonify
(
message
)
response
.
status_code
=
404
return
response
return
response
@
app
.
errorhandler
(
404
)
@
app
.
errorhandler
(
404
)
def
error404
(
msg
=
""
):
def
error404
(
error
):
return
"404: Resource not found.
\
n
%s
\
n
"
%
msg
,
404
if
error
.
description
is
None
:
message
=
{
@
app
.
errorhandler
(
501
)
'code'
:
404
,
def
error501
(
msg
=
""
):
'name'
:
'NotFound'
,
return
"501: Internal Error. %s
\
n
"
%
msg
,
501
'message'
:
'Resource not found: '
+
request
.
url
,
}
else
:
message
=
error
.
description
response
=
jsonify
(
message
)
response
.
status_code
=
404
@
app
.
errorhandler
(
412
)
return
response
def
error412
(
msg
):
return
"412: Precondition Failed: %s.
\
n
"
%
msg
,
412
@
app
.
errorhandler
(
400
)
@
app
.
errorhandler
(
400
)
def
error400
(
msg
):
def
error400
(
error
):
return
"400: Bad Request"
,
400
if
error
.
description
is
None
:
message
=
{
'code'
:
400
,
'name'
:
'BadRequest'
,
'message'
:
'The request could not be understood by the server, you probably provided wrong parameters.'
}
else
:
message
=
error
.
description
response
=
jsonify
(
message
)
response
.
status_code
=
400
return
response
def
writefile
(
path
,
content
,
mode
=
0640
):
with
open
(
path
,
'w'
)
as
fd
:
fd
.
write
(
content
)
os
.
chmod
(
path
,
mode
)
@
app
.
before_request
@
app
.
before_request
def
before_request
():
def
before_request
():
if
request
.
path
.
startswith
(
'/static/'
)
\
if
not
request
.
path
.
startswith
(
'/admin/'
):
or
request
.
path
.
startswith
==
'/get/'
:
return
return
if
request
.
path
==
'/
configure'
or
request
.
path
==
'
/setpassword'
:
if
request
.
path
==
'/
admin/configure'
or
request
.
path
==
'/admin
/setpassword'
:
# check if password file exists, if yes go to index
# check if password file exists, if yes go to index
if
find_user
(
'admin'
):
if
find_user
(
'admin'
):
return
redirect
(
url_for
(
'home'
))
return
redirect
(
url_for
(
'home'
))
...
@@ -61,171 +118,265 @@ def before_request():
...
@@ -61,171 +118,265 @@ def before_request():
return
redirect
(
url_for
(
'configure'
))
return
redirect
(
url_for
(
'configure'
))
g
.
user
=
current_user
g
.
user
=
current_user
@
app
.
route
(
'/'
)
def
index
():
# page to list certificates, also connection link
data_list
=
cert_tools
.
getCertificateList
()
return
render_template
(
"index.html"
,
data_list
=
data_list
)
@
app
.
route
(
'/get/<string:name>'
,
methods
=
[
'GET'
])
# Routes for certificate Authority
def
getfile
(
name
):
ca_name
=
os
.
path
.
basename
(
app
.
config
.
ca
.
certificate
)
@
app
.
route
(
'/crl'
,
methods
=
[
'GET'
])
crl_name
=
os
.
path
.
basename
(
app
.
config
.
ca
.
ca_crl
)
def
get_crl
():
if
name
==
ca_name
:
return
send_file
(
app
.
config
.
ca
.
certificate
,
if
os
.
path
.
exists
(
app
.
config
.
ca
.
ca_crl
):
attachment_filename
=
ca_name
,
as_attachment
=
True
)
elif
name
==
crl_name
:
return
send_file
(
app
.
config
.
ca
.
ca_crl
,
return
send_file
(
app
.
config
.
ca
.
ca_crl
,
attachment_filename
=
crl_name
,
attachment_filename
=
os
.
path
.
basename
(
app
.
config
.
ca
.
ca_crl
)
,
as_attachment
=
True
)
as_attachment
=
True
)
else
:
else
:
# name is like f9c4-4a04-8bad-70bb45c6406e.cert.pem
return
abort
(
404
)
cert_filename
=
cert_tools
.
getFilenameFromHash
(
name
)
cert_file
=
os
.
path
.
join
(
app
.
config
.
cert_dir
,
cert_filename
)
if
os
.
path
.
exists
(
cert_file
)
and
os
.
path
.
isfile
(
cert_file
):
filename
=
os
.
path
.
basename
(
cert_file
)
return
send_file
(
cert_file
,
attachment_filename
=
filename
,
as_attachment
=
True
)
return
abort
(
404
,
name
)
@
app
.
route
(
'/logout'
)
def
logout
():
logout_user
()
return
redirect
(
url_for
(
'home'
))
@
app
.
route
(
'/configure'
,
methods
=
[
'GET'
])
def
configure
():
return
render_template
(
"configure.html"
)
@
app
.
route
(
'/setpassword'
,
methods
=
[
'POST'
])
def
setpassword
():
username
=
'admin'
password
=
request
.
form
.
get
(
'password'
,
''
).
encode
(
'utf-8'
)
if
not
password
:
return
abort
(
412
,
"password not set or empty"
)
find_or_create_user
(
"Admin"
,
"admin"
,
"admin@example.com"
,
username
,
password
)
logout_user
()
return
redirect
(
url_for
(
'home'
))
# Routes for certificate Authority
@
app
.
route
(
'/csr/<string:csr_id>'
,
methods
=
[
'GET'
])
def
signcert
(
key
,
redirect_to
=
''
):
def
get_csr
(
csr_id
):
req_file
=
os
.
path
.
join
(
app
.
config
.
req_dir
,
'%s.csr.pem'
%
key
)
csr_file
=
os
.
path
.
join
(
app
.
config
.
req_dir
,
csr_id
)
if
os
.
path
.
exists
(
req_file
)
and
os
.
path
.
isfile
(
req_file
):
if
os
.
path
.
exists
(
csr_file
)
and
os
.
path
.
isfile
(
csr_file
):
cert_id
=
key
.
split
(
'+'
)[
0
]
return
send_file
(
csr_file
,
try
:
attachment_filename
=
os
.
path
.
basename
(
csr_file
),
cert_tools
.
signCertificate
(
cert_id
,
req_file
)
as_attachment
=
True
)
except
Exception
,
e
:
# XXX - need to check this
raise
# return abort(501, str(e))
else
:
else
:
return
abort
(
403
)
return
abort
(
404
)
flash
(
'Certificate is signed!'
,
'success'
)
if
redirect_to
:
return
redirect
(
url_for
(
redirect_to
))
return
"Certificate is signed"
@
app
.
route
(
'/
request'
,
methods
=
[
'POS
T'
])
@
app
.
route
(
'/
csr'
,
methods
=
[
'PU
T'
])
def
do_reques
t
():
def
request_cer
t
():
csr_content
=
request
.
form
.
get
(
'csr'
,
''
).
encode
(
'utf-8'
)
csr_content
=
request
.
form
.
get
(
'csr'
,
''
).
encode
(
'utf-8'
)
if
not
csr_content
:
if
not
csr_content
:
return
abort
(
400
)
return
abort
(
400
,
error
.
MISSING_PARAM
)
try
:
try
:
# check if a valid csr content
req
=
app
.
config
.
ca
.
readCertificateRequest
(
csr_content
)
req
=
app
.
config
.
ca
.
readCertificateRequest
(
csr_content
)
# XXX - Maybe more check if the csr is valid with CA
except
crypto
.
Error
,
e
:
except
crypto
.
Error
,
e
:
return
abort
(
4
12
,
str
(
e
)
)
return
abort
(
4
00
,
error
.
CSR_FORMAT
)
cert_id
=
str
(
uuid
.
uuid1
())
return
do_requestcert
(
csr_content
)
csr_keyfile
=
'%s+%s.csr.pem'
%
(
cert_id
,
str
(
uuid
.
uuid4
()))
request_file
=
os
.
path
.
join
(
app
.
config
.
req_dir
,
csr_keyfile
)
if
os
.
path
.
exists
(
request_file
):
@
app
.
route
(
'/csr/<string:csr_id>'
,
methods
=
[
'DELETE'
])
# The request already exist
@
authenticated_method
raise
Exception
(
"Certificate Signature Request file should be unique"
)
def
remove_csr
(
csr_id
):
try
:
req_file
=
os
.
path
.
join
(
app
.
config
.
req_dir
,
csr_id
)
writefile
(
request_file
,
csr_content
)
if
os
.
path
.
exists
(
req_file
)
and
os
.
path
.
isfile
(
req_file
):
except
OSError
,
e
:
os
.
unlink
(
req_file
)
raise
else
:
return
abort
(
404
)
return
cert_id
@
app
.
route
(
'/crt/<string:cert_id>'
,
methods
=
[
'GET'
])
def
get_crt
(
cert_id
):
cacert_id
=
os
.
path
.
basename
(
app
.
config
.
ca
.
certificate
)
if
cert_id
==
cacert_id
:
cert_file
=
app
.
config
.
ca
.
certificate
else
:
cert_filename
=
cert_tools
.
getFilenameFromHash
(
name
)
cert_file
=
os
.
path
.
join
(
app
.
config
.
cert_dir
,
cert_filename
)
@
app
.
route
(
'/signcert'
,
methods
=
[
'POST'
])
if
os
.
path
.
exists
(
cert_file
)
and
os
.
path
.
isfile
(
cert_file
):
def
do_signcert
():
return
send_file
(
cert_file
,
"""
attachment_filename
=
os
.
path
.
basename
(
cert_file
),
This method should be called by a list of host allowed, can be used to sign with command line
as_attachment
=
True
)
For security, it's good to only allow local ip (127.0.0.1) or ::1
else
:
"""
return
abort
(
404
,
cert_id
)
@
app
.
route
(
'/crt'
,
methods
=
[
'PUT'
])
@
authenticated_method
def
sign_cert
():
key
=
request
.
form
.
get
(
'key'
,
''
).
encode
(
'utf-8'
)
key
=
request
.
form
.
get
(
'key'
,
''
).
encode
(
'utf-8'
)
if
not
key
:
if
not
key
:
return
abort
(
400
)
return
abort
(
400
)
remote_client
=
request
.
remote_addr
x_forwarded_for_list
=
request
.
headers
.
getlist
(
"X-Forwarded-For"
)
if
remote_client
not
in
app
.
config
.
trusted_host_list
or
\
(
x_forwarded_for_list
and
x_forwarded_for_list
[
0
]
not
in
app
.
config
.
trusted_host_list
):
return
abort
(
403
)
# Forbidden
return
signcert
(
key
)
return
signcert
(
key
)
@
app
.
route
(
'/
renewcert'
,
methods
=
[
'POS
T'
])
@
app
.
route
(
'/
crt/renew'
,
methods
=
[
'PU
T'
])
def
renewcert
(
serial
):
def
renew
_
cert
(
serial
):
"""
"""
this method is used to renew expired certificate.
this method is used to renew expired certificate.
should recieve csr and old cert serial.
should recieve csr and old cert serial.
"""
"""
csr_content
=
request
.
form
.
get
(
'csr'
,
''
).
encode
(
'utf-8'
)
digest
=
request
.
form
.
get
(
'digest'
,
''
)
serial
=
request
.
form
.
get
(
'serial'
,
''
).
encode
(
'utf-8'
)
signature
=
request
.
form
.
get
(
'signature'
,
''
)
return
abort
(
200
,
"Done. The method is not implemented yet!"
)
# payload: a Json string containing certificate to revoke and reason msg
payload_string
=
request
.
form
.
get
(
'payload'
,
''
)
if
not
digest
or
not
signature
or
not
payload_string
:
# Bad parameters
return
abort
(
400
)
try
:
payload
=
json
.
loads
(
payload_string
)
except
ValueError
,
exc
:
return
abort
(
400
,
error
.
JSON_FORMAT
,
str
(
exc
))
if
not
payload
.
has_key
(
'cert'
)
and
payload
.
has_key
(
'csr'
):
return
abort
(
400
)
if
not
digest
in
app
.
config
.
CA_DIGEST_LIST
:
return
abort
(
400
,
error
.
INVALID_DIGEST
,
'%r not in allowed signature hash set: %s'
%
(
digest
,
app
.
config
.
CA_DIGEST_LIST
))
try
:
req
=
app
.
config
.
ca
.
readCertificateRequest
(
payload
[
'csr'
])
except
crypto
.
Error
,
e
:
return
abort
(
400
,
error
.
CSR_FORMAT
)
try
:
cert
=
app
.
config
.
ca
.
readX509
(
payload
[
'cert'
])
except
crypto
.
Error
,
e
:
return
abort
(
400
,
error
.
CERT_FORMAT
)
if
not
app
.
config
.
ca
.
verifyData
(
payload
[
'cert'
],
signature
,
payload
,
digest
):
return
abort
(
400
,
Error
.
SIGNATURE_VERIFICATION
)
if
req
.
get_subject
().
CN
!=
cert
.
get_subject
().
CN
:
return
abort
(
400
,
error
.
CSR_CONTENT_MISMATCH
)
return
do_requestcert
(
payload
[
'csr'
])
@
app
.
route
(
'/
requestrevoke'
,
methods
=
[
'POS
T'
])
@
app
.
route
(
'/
crt/revoke'
,
methods
=
[
'PU
T'
])
def
do_revoke
():
def
request_revoke_crt
():
"""
"""
Revoke method required certificat: serial(Hex), name and reason
Revoke method required certificat: serial(Hex), name and reason
'name' is used here to add more verification
'name' is used here to add more verification
'reason' is a message that say why this certificate should be revoked
'reason' is a message that say why this certificate should be revoked
"""
"""
serial
=
request
.
form
.
get
(
'serial'
,
''
).
upper
(
)
digest
=
request
.
form
.
get
(
'digest'
,
''
)
name
=
request
.
form
.
get
(
'nam
e'
,
''
)
signature
=
request
.
form
.
get
(
'signatur
e'
,
''
)
#
A msg explaining why the certificate should be revoked
#
payload: a Json string containing certificate to revoke and reason msg
reason
=
request
.
form
.
get
(
'reason
'
,
''
)
payload_string
=
request
.
form
.
get
(
'payload
'
,
''
)
if
not
serial
or
not
name
or
not
reason
:
if
not
digest
or
not
signature
or
not
payload_string
:
# Bad parameters
# Bad parameters
return
abort
(
400
)
return
abort
(
400
)
if
len
(
serial
)
==
1
:
try
:
serial
=
'0%s'
%
serial
payload
=
json
.
loads
(
payload_string
)
except
ValueError
:
return
abort
(
400
)
if
not
payload
.
has_key
(
'cert'
)
and
payload
.
has_key
(
'reason'
):
return
abort
(
400
)
if
not
digest
in
app
.
config
.
CA_DIGEST_LIST
:
return
abort
(
400
)
try
:
cert
=
app
.
config
.
ca
.
readX509
(
payload
[
'cert'
])
except
crypto
.
Error
,
e
:
return
abort
(
400
,
error
.
CERT_FORMAT
)
if
not
app
.
config
.
ca
.
verifyData
(
payload
[
'cert'
],
signature
,
payload
,
digest
):
return
abort
(
400
,
Error
.
SIGNATURE_VERIFICATION
)
if
cert_tools
.
addRevokeRequest
(
payload
[
'cert'
],
name
,
payload
[
'reason'
]):
message
=
{
"code"
:
201
,
"name"
:
"Created"
,
"message"
:
"Content created - Certificate was revoked"
}
if
cert_tools
.
addRevokeRequest
(
serial
,
name
,
reason
):
response
=
Response
(
message
,
return
"Certificate revocation created"
status
=
201
,
mimetype
=
'application/json'
)
return
response
else
:
else
:
return
abort
(
4
12
,
"Parameters not valid for the request"
)
return
abort
(
4
04
)
def
do_requestcert
(
csr
):
key
=
str
(
uuid
.
uuid1
())
csr_filename
=
'%s.csr.pem'
%
key
csr_file
=
os
.
path
.
join
(
app
.
config
.
req_dir
,
csr_filename
)
fd
=
os
.
open
(
csr_file
,
os
.
O_CREAT
|
os
.
O_WRONLY
|
os
.
O_EXCL
|
os
.
O_TRUNC
,
0640
)
try
:
os
.
write
(
fd
,
csr
)
finally
:
os
.
close
(
fd
)
message
=
{
"code"
:
201
,
"name"
:
"Created"
,
"message"
:
"Content created - Certificate Signing Request was accepted"
}
response
=
Response
(
message
,
status
=
201
,
mimetype
=
'application/json'
)
response
.
headers
[
'Location'
]
=
url_for
(
'get_csr'
,
_external
=
True
,
cert_id
=
csr_filename
)
#Manage routes (access admin)
return
response
def
signcert
(
csr_key
,
redirect_to
=
''
):
req_file
=
os
.
path
.
join
(
app
.
config
.
req_dir
,
csr_key
)
if
os
.
path
.
exists
(
req_file
)
and
os
.
path
.
isfile
(
req_file
):
cert_id
=
csr_key
.
split
(
'.'
)[
0
]
# cert_id = csr_key[:-8]
try
:
cert_name
=
cert_tools
.
signCertificate
(
req_file
,
cert_id
)
except
Exception
,
e
:
# XXX - need to check this
raise
# return abort(501, str(e))
else
:
return
abort
(
404
)
# XXX - to remove
flash
(
'Certificate is signed!'
,
'success'
)
if
redirect_to
:
return
redirect
(
url_for
(
redirect_to
))
message
=
{
"code"
:
201
,
"name"
:
"Created"
,
"message"
:
"Content created - Certificate was signed"
}
response
=
Response
(
message
,
status
=
201
,
mimetype
=
'application/json'
)
response
.
headers
[
'Location'
]
=
url_for
(
'get_crt'
,
_external
=
True
,
cert_id
=
cert_name
)
return
response
#Manage routes (Authentication required) - Flask APP
@
app
.
route
(
'/'
)
def
index
():
# page to list certificates, also connection link
data_list
=
cert_tools
.
getCertificateList
()
return
render_template
(
"index.html"
,
data_list
=
data_list
)
@
app
.
route
(
'/admin/logout'
)
def
logout
():
logout_user
()
return
redirect
(
url_for
(
'home'
))
@
app
.
route
(
'/admin/configure'
,
methods
=
[
'GET'
])
def
configure
():
return
render_template
(
"configure.html"
)
@
app
.
route
(
'/admin/setpassword'
,
methods
=
[
'POST'
])
def
setpassword
():
username
=
'admin'
password
=
request
.
form
.
get
(
'password'
,
''
).
encode
(
'utf-8'
)
if
not
password
:
return
abort
(
412
,
"password not set or empty"
)
find_or_create_user
(
"Admin"
,
"admin"
,
"admin@example.com"
,
username
,
password
)
logout_user
()
return
redirect
(
url_for
(
'home'
))
@
app
.
route
(
'/manage'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
manage'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
manage_home
():
def
manage_home
():
data_list
=
cert_tools
.
getCertificateRequestList
()
data_list
=
cert_tools
.
getCertificateRequestList
()
...
@@ -237,7 +388,7 @@ def manage_home():
...
@@ -237,7 +388,7 @@ def manage_home():
)
)
return
render_template
(
'manage_page.html'
,
data_list
=
data_list
,
counter
=
c
)
return
render_template
(
'manage_page.html'
,
data_list
=
data_list
,
counter
=
c
)
@
app
.
route
(
'/profile'
,
methods
=
[
'GET'
,
'POST'
])
@
app
.
route
(
'/
admin/
profile'
,
methods
=
[
'GET'
,
'POST'
])
@
login_required
@
login_required
def
user_profile
():
def
user_profile
():
form
=
UserProfileForm
(
request
.
form
,
obj
=
current_user
)
form
=
UserProfileForm
(
request
.
form
,
obj
=
current_user
)
...
@@ -253,7 +404,7 @@ def user_profile():
...
@@ -253,7 +404,7 @@ def user_profile():
return
render_template
(
'user_profile.html'
,
return
render_template
(
'user_profile.html'
,
form
=
form
)
form
=
form
)
@
app
.
route
(
'/viewcert'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
viewcert'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
viewcert
():
def
viewcert
():
type_dict
=
{
type_dict
=
{
...
@@ -282,7 +433,7 @@ def viewcert():
...
@@ -282,7 +433,7 @@ def viewcert():
return
render_template
(
'view_cert.html'
,
content
=
content
,
name
=
name
,
cert_type
=
cert_type
)
return
render_template
(
'view_cert.html'
,
content
=
content
,
name
=
name
,
cert_type
=
cert_type
)
@
app
.
route
(
'/signcert_web'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
signcert_web'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
do_signcert_web
():
def
do_signcert_web
():
filename
=
request
.
args
.
get
(
'name'
,
''
).
encode
(
'utf-8'
)
filename
=
request
.
args
.
get
(
'name'
,
''
).
encode
(
'utf-8'
)
...
@@ -290,7 +441,7 @@ def do_signcert_web():
...
@@ -290,7 +441,7 @@ def do_signcert_web():
return
abort
(
412
)
return
abort
(
412
)
return
signcert
(
filename
[:
-
8
],
'manage'
)
return
signcert
(
filename
[:
-
8
],
'manage'
)
@
app
.
route
(
'/deletecsr'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
deletecsr'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
deletecsr
():
def
deletecsr
():
"""
"""
...
@@ -307,21 +458,21 @@ def deletecsr():
...
@@ -307,21 +458,21 @@ def deletecsr():
else
:
else
:
return
abort
(
404
)
return
abort
(
404
)
@
app
.
route
(
'/signed_certs'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
signed_certs'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
signed_certificate_list
():
def
signed_certificate_list
():
# page to list certificates, also connection link
# page to list certificates, also connection link
data_list
=
cert_tools
.
getCertificateList
(
False
)
data_list
=
cert_tools
.
getCertificateList
(
False
)
return
render_template
(
"signed_certs.html"
,
data_list
=
data_list
)
return
render_template
(
"signed_certs.html"
,
data_list
=
data_list
)
@
app
.
route
(
'/revoked_certs'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
revoked_certs'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
revoked_certificate_list
():
def
revoked_certificate_list
():
# page to list certificates, also connection link
# page to list certificates, also connection link
data_list
=
cert_tools
.
getRevokedCertificateList
()
data_list
=
cert_tools
.
getRevokedCertificateList
()
return
render_template
(
"revoked_certs.html"
,
data_list
=
data_list
)
return
render_template
(
"revoked_certs.html"
,
data_list
=
data_list
)
@
app
.
route
(
'/revoke_cert'
,
methods
=
[
'GET'
,
'POST'
])
@
app
.
route
(
'/
admin/
revoke_cert'
,
methods
=
[
'GET'
,
'POST'
])
@
login_required
@
login_required
def
revoke_cert
():
def
revoke_cert
():
redirect_url
=
'signed_certs'
redirect_url
=
'signed_certs'
...
@@ -338,13 +489,13 @@ def revoke_cert():
...
@@ -338,13 +489,13 @@ def revoke_cert():
return
abort
(
404
)
return
abort
(
404
)
@
app
.
route
(
'/revocation_requests'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
revocation_requests'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
revocation_request_list
():
def
revocation_request_list
():
data_list
=
cert_tools
.
getRevocationRequestList
()
data_list
=
cert_tools
.
getRevocationRequestList
()
return
render_template
(
"revocation_requests.html"
,
data_list
=
data_list
)
return
render_template
(
"revocation_requests.html"
,
data_list
=
data_list
)
@
app
.
route
(
'/reject_revocation_request'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
reject_revocation_request'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
reject_revocation_request
():
def
reject_revocation_request
():
req_id
=
request
.
args
.
get
(
'request_id'
,
''
)
req_id
=
request
.
args
.
get
(
'request_id'
,
''
)
...
@@ -357,7 +508,7 @@ def reject_revocation_request():
...
@@ -357,7 +508,7 @@ def reject_revocation_request():
return
redirect
(
url_for
(
'revocation_requests'
))
return
redirect
(
url_for
(
'revocation_requests'
))
@
app
.
route
(
'/view_logs'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
view_logs'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
view_logs
():
def
view_logs
():
content
=
""
content
=
""
...
@@ -370,7 +521,7 @@ def view_logs():
...
@@ -370,7 +521,7 @@ def view_logs():
content
=
tail
(
f
,
500
)
content
=
tail
(
f
,
500
)
return
render_template
(
"view_logs.html"
,
content
=
content
,
filename
=
app
.
config
.
log_file
)
return
render_template
(
"view_logs.html"
,
content
=
content
,
filename
=
app
.
config
.
log_file
)
@
app
.
route
(
'/generate_crl'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
admin/
generate_crl'
,
methods
=
[
'GET'
])
@
login_required
@
login_required
def
generate_crl
():
def
generate_crl
():
app
.
config
.
ca
.
genCertificateRevocationList
()
app
.
config
.
ca
.
genCertificateRevocationList
()
...
@@ -378,6 +529,6 @@ def generate_crl():
...
@@ -378,6 +529,6 @@ def generate_crl():
app
.
add_url_rule
(
'/'
,
'home'
,
index
)
app
.
add_url_rule
(
'/'
,
'home'
,
index
)
app
.
add_url_rule
(
'/manage'
,
'manage'
,
manage_home
)
app
.
add_url_rule
(
'/admin/manage'
,
'manage'
,
manage_home
)
app
.
add_url_rule
(
'/signed_certs'
,
'signed_certs'
,
signed_certificate_list
)
app
.
add_url_rule
(
'/admin/signed_certs'
,
'signed_certs'
,
signed_certificate_list
)
app
.
add_url_rule
(
'/revocation_requests'
,
'revocation_requests'
,
revocation_request_list
)
app
.
add_url_rule
(
'/admin/revocation_requests'
,
'revocation_requests'
,
revocation_request_list
)
\ No newline at end of file
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment