Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caucase
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
nexedi
caucase
Commits
8ce08bf9
Commit
8ce08bf9
authored
Jul 13, 2018
by
Vincent Pelletier
Committed by
Vincent Pelletier
Sep 26, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
all: More python3 adaptations.
What was not picked up by 2to3.
parent
7f9e56cf
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
286 additions
and
220 deletions
+286
-220
caucase/ca.py
caucase/ca.py
+19
-18
caucase/cli.py
caucase/cli.py
+36
-25
caucase/client.py
caucase/client.py
+18
-19
caucase/http.py
caucase/http.py
+39
-33
caucase/http_wsgibase.py
caucase/http_wsgibase.py
+6
-5
caucase/storage.py
caucase/storage.py
+17
-12
caucase/test.py
caucase/test.py
+96
-75
caucase/utils.py
caucase/utils.py
+24
-10
caucase/wsgi.py
caucase/wsgi.py
+31
-23
No files found.
caucase/ca.py
View file @
8ce08bf9
...
...
@@ -19,6 +19,7 @@
Caucase - Certificate Authority for Users, Certificate Authority for SErvices
"""
from
__future__
import
absolute_import
from
binascii
import
hexlify
,
unhexlify
import
datetime
import
json
import
os
...
...
@@ -55,7 +56,7 @@ _SUBJECT_OID_DICT = {
'GN'
:
x509
.
oid
.
NameOID
.
GIVEN_NAME
,
# pylint: enable=bad-whitespace
}
_BACKUP_MAGIC
=
'caucase
\
0
'
_BACKUP_MAGIC
=
b
'caucase
\
0
'
_CONFIG_NAME_AUTO_SIGN_CSR_AMOUNT
=
'auto_sign_csr_amount'
def
Extension
(
value
,
critical
):
...
...
@@ -227,9 +228,9 @@ class CertificateAuthority(object):
# Note: requested_amount is None when a known CSR is re-submitted
csr_id
,
requested_amount
=
self
.
_storage
.
appendCertificateSigningRequest
(
csr_pem
=
csr_pem
,
key_id
=
x509
.
SubjectKeyIdentifier
.
from_public_key
(
key_id
=
hexlify
(
x509
.
SubjectKeyIdentifier
.
from_public_key
(
csr
.
public_key
(),
).
digest
.
encode
(
'hex'
),
).
digest
),
override_limits
=
override_limits
,
)
if
requested_amount
is
not
None
and
\
...
...
@@ -632,8 +633,8 @@ class CertificateAuthority(object):
current_crt_pem
=
utils
.
dump_certificate
(
key_pair
[
'crt'
])
result
.
append
(
utils
.
wrap
(
{
'old_pem'
:
previous_crt_pem
,
'new_pem'
:
current_crt_pem
,
'old_pem'
:
utils
.
toUnicode
(
previous_crt_pem
)
,
'new_pem'
:
utils
.
toUnicode
(
current_crt_pem
)
,
},
previous_key
,
self
.
digest_list
[
0
],
...
...
@@ -799,31 +800,31 @@ class UserCertificateAuthority(CertificateAuthority):
continue
public_key
=
crt
.
public_key
()
key_list
.
append
({
'id'
:
x509
.
SubjectKeyIdentifier
.
from_public_ke
y
(
public_key
,
)
.
digest
.
encode
(
'hex'
),
'id'
:
utils
.
toUnicode
(
hexlif
y
(
x509
.
SubjectKeyIdentifier
.
from_public_key
(
public_key
).
digest
,
)),
'cipher'
:
{
'name'
:
'rsa_oaep_sha1_mgf1_sha1'
,
},
'key'
:
public_key
.
encrypt
(
'key'
:
utils
.
toUnicode
(
hexlify
(
public_key
.
encrypt
(
signing_key
+
symetric_key
,
OAEP
(
mgf
=
MGF1
(
algorithm
=
hashes
.
SHA1
()),
algorithm
=
hashes
.
SHA1
(),
label
=
None
,
),
)
.
encode
(
'hex'
),
)
)
),
})
if
not
key_list
:
# No users yet, backup is meaningless
return
False
header
=
json
.
dumps
({
header
=
utils
.
toBytes
(
json
.
dumps
({
'cipher'
:
{
'name'
:
'aes256_cbc_pkcs7_hmac_10M_sha256'
,
'parameter'
:
iv
.
encode
(
'hex'
),
'parameter'
:
utils
.
toUnicode
(
hexlify
(
iv
)
),
},
'key_list'
:
key_list
,
})
})
)
padder
=
padding
.
PKCS7
(
128
).
padder
()
write
(
_BACKUP_MAGIC
)
write
(
struct
.
pack
(
'<I'
,
len
(
header
)))
...
...
@@ -877,11 +878,11 @@ class UserCertificateAuthority(CertificateAuthority):
if
header
[
'cipher'
][
'name'
]
!=
'aes256_cbc_pkcs7_hmac_10M_sha256'
:
raise
ValueError
(
'Unrecognised symetric cipher'
)
private_key
=
utils
.
load_privatekey
(
key_pem
)
key_id
=
x509
.
SubjectKeyIdentifier
.
from_public_key
(
key_id
=
hexlify
(
x509
.
SubjectKeyIdentifier
.
from_public_key
(
private_key
.
public_key
(),
).
digest
.
encode
(
'hex'
)
).
digest
)
symetric_key_list
=
[
x
for
x
in
header
[
'key_list'
]
if
x
[
'id'
]
==
key_id
x
for
x
in
header
[
'key_list'
]
if
utils
.
toBytes
(
x
[
'id'
])
==
key_id
]
if
not
symetric_key_list
:
raise
ValueError
(
...
...
@@ -891,7 +892,7 @@ class UserCertificateAuthority(CertificateAuthority):
if
symetric_key_entry
[
'cipher'
][
'name'
]
!=
'rsa_oaep_sha1_mgf1_sha1'
:
raise
ValueError
(
'Unrecognised asymetric cipher'
)
both_keys
=
private_key
.
decrypt
(
symetric_key_entry
[
'key'
].
decode
(
'hex'
),
unhexlify
(
symetric_key_entry
[
'key'
]
),
OAEP
(
mgf
=
MGF1
(
algorithm
=
hashes
.
SHA1
()),
algorithm
=
hashes
.
SHA1
(),
...
...
@@ -902,7 +903,7 @@ class UserCertificateAuthority(CertificateAuthority):
raise
ValueError
(
'Invalid key length'
)
decryptor
=
Cipher
(
algorithms
.
AES
(
both_keys
[
32
:]),
modes
.
CBC
(
header
[
'cipher'
][
'parameter'
].
decode
(
'hex'
)),
modes
.
CBC
(
unhexlify
(
header
[
'cipher'
][
'parameter'
]
)),
backend
=
_cryptography_backend
,
).
decryptor
()
unpadder
=
padding
.
PKCS7
(
128
).
unpadder
()
...
...
caucase/cli.py
View file @
8ce08bf9
...
...
@@ -20,6 +20,7 @@ Caucase - Certificate Authority for Users, Certificate Authority for SErvices
"""
from
__future__
import
absolute_import
,
print_function
import
argparse
from
binascii
import
hexlify
import
datetime
import
httplib
import
json
...
...
@@ -102,7 +103,7 @@ class CLICaucaseClient(object):
"""
for
csr_id
,
csr_path
in
csr_id_path_list
:
csr_pem
=
self
.
_client
.
getCertificateSigningRequest
(
int
(
csr_id
))
with
open
(
csr_path
,
'a'
)
as
csr_file
:
with
open
(
csr_path
,
'a
b
'
)
as
csr_file
:
csr_file
.
write
(
csr_pem
)
def
getCRT
(
self
,
warning
,
error
,
crt_id_path_list
,
ca_list
):
...
...
@@ -157,7 +158,7 @@ class CLICaucaseClient(object):
)
error
=
True
continue
with
open
(
crt_path
,
'a'
)
as
crt_file
:
with
open
(
crt_path
,
'a
b
'
)
as
crt_file
:
crt_file
.
write
(
crt_pem
)
return
warning
,
error
...
...
@@ -228,11 +229,17 @@ class CLICaucaseClient(object):
key_len
=
key_len
,
)
if
key_path
is
None
:
with
open
(
crt_path
,
'w'
)
as
crt_file
:
with
open
(
crt_path
,
'w
b
'
)
as
crt_file
:
crt_file
.
write
(
new_key_pem
)
crt_file
.
write
(
new_crt_pem
)
else
:
with
open
(
crt_path
,
'w'
)
as
crt_file
,
open
(
key_path
,
'w'
)
as
key_file
:
with
open
(
crt_path
,
'wb'
,
)
as
crt_file
,
open
(
key_path
,
'wb'
,
)
as
key_file
:
key_file
.
write
(
new_key_pem
)
crt_file
.
write
(
new_crt_pem
)
updated
=
True
...
...
@@ -250,7 +257,7 @@ class CLICaucaseClient(object):
),
)
for
entry
in
self
.
_client
.
getPendingCertificateRequestList
():
csr
=
utils
.
load_certificate_request
(
entry
[
'csr'
]
)
csr
=
utils
.
load_certificate_request
(
utils
.
toBytes
(
entry
[
'csr'
])
)
print
(
'%20s | %r'
%
(
entry
[
'id'
],
...
...
@@ -264,7 +271,7 @@ class CLICaucaseClient(object):
--sign-csr
"""
for
csr_id
in
csr_id_list
:
self
.
_client
.
createCertificate
(
int
(
csr_id
))
self
.
_client
.
createCertificate
(
int
(
utils
.
toUnicode
(
csr_id
)
))
def
signCSRWith
(
self
,
csr_id_path_list
):
"""
...
...
@@ -272,7 +279,7 @@ class CLICaucaseClient(object):
"""
for
csr_id
,
csr_path
in
csr_id_path_list
:
self
.
_client
.
createCertificate
(
int
(
csr_id
),
int
(
utils
.
toUnicode
(
csr_id
)
),
template_csr
=
utils
.
getCertRequest
(
csr_path
),
)
...
...
@@ -763,7 +770,7 @@ def updater(argv=None, until=utils.until):
# Still here ? Ok, wait a bit and try again.
until
(
datetime
.
datetime
.
utcnow
()
+
datetime
.
timedelta
(
0
,
60
))
else
:
with
open
(
args
.
crt
,
'a'
)
as
crt_file
:
with
open
(
args
.
crt
,
'a
b
'
)
as
crt_file
:
crt_file
.
write
(
crt_pem
)
updated
=
True
break
...
...
@@ -797,10 +804,11 @@ def updater(argv=None, until=utils.until):
if
RetryingCaucaseClient
.
updateCRLFile
(
ca_url
,
args
.
crl
,
ca_crt_list
):
print
(
'Got new CRL'
)
updated
=
True
next_deadline
=
min
(
next_deadline
,
utils
.
load_crl
(
open
(
args
.
crl
).
read
(),
ca_crt_list
).
next_update
,
)
with
open
(
args
.
crl
,
'rb'
)
as
crl_file
:
next_deadline
=
min
(
next_deadline
,
utils
.
load_crl
(
crli_file
.
read
(),
ca_crt_list
).
next_update
,
)
if
args
.
crt
:
crt_pem
,
key_pem
,
key_path
=
utils
.
getKeyPair
(
args
.
crt
,
args
.
key
)
crt
=
utils
.
load_certificate
(
crt_pem
,
ca_crt_list
,
None
)
...
...
@@ -812,16 +820,16 @@ def updater(argv=None, until=utils.until):
key_len
=
args
.
key_len
,
)
if
key_path
is
None
:
with
open
(
args
.
crt
,
'w'
)
as
crt_file
:
with
open
(
args
.
crt
,
'w
b
'
)
as
crt_file
:
crt_file
.
write
(
new_key_pem
)
crt_file
.
write
(
new_crt_pem
)
else
:
with
open
(
args
.
crt
,
'w'
,
'w
b
'
,
)
as
crt_file
,
open
(
key_path
,
'w'
,
'w
b
'
,
)
as
key_file
:
key_file
.
write
(
new_key_pem
)
crt_file
.
write
(
new_crt_pem
)
...
...
@@ -894,11 +902,11 @@ def rerequest(argv=None):
key_pem
=
utils
.
dump_privatekey
(
key
)
orig_umask
=
os
.
umask
(
0o177
)
try
:
with
open
(
args
.
key
,
'w'
)
as
key_file
:
with
open
(
args
.
key
,
'w
b
'
)
as
key_file
:
key_file
.
write
(
key_pem
)
finally
:
os
.
umask
(
orig_umask
)
with
open
(
args
.
csr
,
'w'
)
as
csr_file
:
with
open
(
args
.
csr
,
'w
b
'
)
as
csr_file
:
csr_file
.
write
(
csr_pem
)
def
key_id
(
argv
=
None
):
...
...
@@ -926,17 +934,20 @@ def key_id(argv=None):
)
args
=
parser
.
parse_args
(
argv
)
for
key_path
in
args
.
private_key
:
print
(
key_path
,
x509
.
SubjectKeyIdentifier
.
from_public_key
(
utils
.
load_privatekey
(
open
(
key_path
).
read
()).
public_key
(),
).
digest
.
encode
(
'hex'
),
)
with
open
(
key_path
,
'rb'
)
as
key_file
:
print
(
key_path
,
utils
.
toUnicode
(
hexlify
(
x509
.
SubjectKeyIdentifier
.
from_public_key
(
utils
.
load_privatekey
(
key_file
.
read
()).
public_key
(),
).
digest
,
)),
)
for
backup_path
in
args
.
backup
:
print
(
backup_path
)
with
open
(
backup_path
)
as
backup_file
:
with
open
(
backup_path
,
'rb'
)
as
backup_file
:
magic
=
backup_file
.
read
(
8
)
if
magic
!=
'caucase
\
0
'
:
if
magic
!=
b
'caucase
\
0
'
:
raise
ValueError
(
'Invalid backup magic string'
)
header_len
,
=
struct
.
unpack
(
'<I'
,
...
...
caucase/client.py
View file @
8ce08bf9
...
...
@@ -69,7 +69,7 @@ class CaucaseClient(object):
"""
if
not
os
.
path
.
exists
(
ca_crt_path
):
ca_pem
=
cls
(
ca_url
=
url
).
getCACertificate
()
with
open
(
ca_crt_path
,
'w'
)
as
ca_crt_file
:
with
open
(
ca_crt_path
,
'w
b
'
)
as
ca_crt_file
:
ca_crt_file
.
write
(
ca_pem
)
updated
=
True
else
:
...
...
@@ -85,8 +85,8 @@ class CaucaseClient(object):
cls
(
ca_url
=
url
,
ca_crt_pem_list
=
ca_pem_list
).
getCACertificateChain
(),
)
if
ca_pem_list
!=
loaded_ca_pem_list
:
data
=
''
.
join
(
ca_pem_list
)
with
open
(
ca_crt_path
,
'w'
)
as
ca_crt_file
:
data
=
b
''
.
join
(
ca_pem_list
)
with
open
(
ca_crt_path
,
'w
b
'
)
as
ca_crt_file
:
ca_crt_file
.
write
(
data
)
updated
=
True
return
updated
...
...
@@ -107,13 +107,13 @@ class CaucaseClient(object):
Return whether an update happened.
"""
if
os
.
path
.
exists
(
crl_path
):
my_crl
=
utils
.
load_crl
(
open
(
crl_path
).
read
(),
ca_list
)
my_crl
=
utils
.
load_crl
(
open
(
crl_path
,
'rb'
).
read
(),
ca_list
)
else
:
my_crl
=
None
latest_crl_pem
=
cls
(
ca_url
=
url
).
getCertificateRevocationList
()
latest_crl
=
utils
.
load_crl
(
latest_crl_pem
,
ca_list
)
if
my_crl
is
None
or
latest_crl
.
signature
!=
my_crl
.
signature
:
with
open
(
crl_path
,
'w'
)
as
crl_file
:
with
open
(
crl_path
,
'w
b
'
)
as
crl_file
:
crl_file
.
write
(
latest_crl_pem
)
return
True
return
False
...
...
@@ -138,7 +138,11 @@ class CaucaseClient(object):
ssl_context
=
ssl
.
create_default_context
(
# unicode object needed as we use PEM, otherwise create_default_context
# expects DER.
cadata
=
''
.
join
(
http_ca_crt_pem_list
).
decode
(
'ascii'
)
if
http_ca_crt_pem_list
else
None
,
cadata
=
(
utils
.
toUnicode
(
''
.
join
(
http_ca_crt_pem_list
))
if
http_ca_crt_pem_list
else
None
),
)
if
not
http_ca_crt_pem_list
:
ssl_context
.
check_hostname
=
False
...
...
@@ -191,13 +195,7 @@ class CaucaseClient(object):
"""
[AUTHENTICATED] Retrieve all pending CSRs.
"""
return
[
{
y
.
encode
(
'ascii'
):
z
.
encode
(
'ascii'
)
if
isinstance
(
z
,
unicode
)
else
z
for
y
,
z
in
x
.
iteritems
()
}
for
x
in
json
.
loads
(
self
.
_https
(
'GET'
,
'/csr'
))
]
return
json
.
loads
(
self
.
_https
(
'GET'
,
'/csr'
))
def
createCertificateSigningRequest
(
self
,
csr
):
"""
...
...
@@ -254,14 +252,14 @@ class CaucaseClient(object):
continue
if
not
found
:
found
=
utils
.
load_ca_certificate
(
payload
[
'old_pem'
].
encode
(
'ascii'
),
utils
.
toBytes
(
payload
[
'old_pem'
]
),
)
==
trust_anchor
if
found
:
if
utils
.
load_ca_certificate
(
payload
[
'old_pem'
].
encode
(
'ascii'
),
utils
.
toBytes
(
payload
[
'old_pem'
]
),
)
!=
previous_ca
:
raise
ValueError
(
'CA signature chain broken'
)
new_pem
=
payload
[
'new_pem'
].
encode
(
'ascii'
)
new_pem
=
utils
.
toBytes
(
payload
[
'new_pem'
]
)
result
.
append
(
new_pem
)
previous_ca
=
utils
.
load_ca_certificate
(
new_pem
)
return
result
...
...
@@ -279,8 +277,8 @@ class CaucaseClient(object):
json
.
dumps
(
utils
.
wrap
(
{
'crt_pem'
:
utils
.
dump_certificate
(
old_crt
),
'renew_csr_pem'
:
utils
.
dump_certificate_request
(
'crt_pem'
:
utils
.
toUnicode
(
utils
.
dump_certificate
(
old_crt
)
),
'renew_csr_pem'
:
utils
.
toUnicode
(
utils
.
dump_certificate_request
(
x509
.
CertificateSigningRequestBuilder
(
).
subject_name
(
# Note: caucase server ignores this, but cryptography
...
...
@@ -291,7 +289,7 @@ class CaucaseClient(object):
algorithm
=
utils
.
DEFAULT_DIGEST_CLASS
(),
backend
=
_cryptography_backend
,
),
),
)
)
,
},
old_key
,
utils
.
DEFAULT_DIGEST
,
...
...
@@ -307,6 +305,7 @@ class CaucaseClient(object):
[ANONYMOUS] if key is provided.
[AUTHENTICATED] if key is missing.
"""
crt
=
utils
.
toUnicode
(
crt
)
if
key
:
method
=
self
.
_http
data
=
utils
.
wrap
(
...
...
caucase/http.py
View file @
8ce08bf9
...
...
@@ -70,7 +70,7 @@ def _createKey(path):
"""
return
os
.
fdopen
(
os
.
open
(
path
,
os
.
O_WRONLY
|
os
.
O_CREAT
,
0o600
),
'w'
,
'w
b
'
,
)
class
ThreadingWSGIServer
(
ThreadingMixIn
,
WSGIServer
):
...
...
@@ -236,7 +236,7 @@ def getSSLContext(
# implementation cross-check would have been nice.
#ssl_context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF
ssl_context
.
load_verify_locations
(
cadata
=
cau
.
getCACertificate
().
decode
(
'ascii'
),
cadata
=
utils
.
toUnicode
(
cau
.
getCACertificate
()
),
)
http_cas_certificate_list
=
http_cas
.
getCACertificateList
()
threshold_delta
=
datetime
.
timedelta
(
threshold
,
0
)
...
...
@@ -500,12 +500,12 @@ def main(argv=None, until=utils.until):
)
args
=
parser
.
parse_args
(
argv
)
base_url
=
u'http://'
+
args
.
netloc
.
decode
(
'ascii'
)
base_url
=
u'http://'
+
utils
.
toUnicode
(
args
.
netloc
)
parsed_base_url
=
urlparse
(
base_url
)
hostname
=
parsed_base_url
.
hostname
name_constraints_permited
=
[]
name_constraints_excluded
=
[]
hostname_dnsname
=
hostname
.
decode
(
'ascii'
)
hostname_dnsname
=
utils
.
toUnicode
(
hostname
)
try
:
hostname_ip_address
=
ipaddress
.
ip_address
(
hostname_dnsname
)
except
ValueError
:
...
...
@@ -615,7 +615,7 @@ def main(argv=None, until=utils.until):
crt_life_time
=
args
.
service_crt_validity
,
)
if
os
.
path
.
exists
(
args
.
cors_key_store
):
with
open
(
args
.
cors_key_store
)
as
cors_key_file
:
with
open
(
args
.
cors_key_store
,
'rb'
)
as
cors_key_file
:
cors_secret_list
=
json
.
load
(
cors_key_file
)
else
:
cors_secret_list
=
[]
...
...
@@ -761,7 +761,7 @@ def main(argv=None, until=utils.until):
tmp_backup_fd
,
tmp_backup_path
=
tempfile
.
mkstemp
(
prefix
=
'caucase_backup_'
,
)
with
os
.
fdopen
(
tmp_backup_fd
,
'w'
)
as
backup_file
:
with
os
.
fdopen
(
tmp_backup_fd
,
'w
b
'
)
as
backup_file
:
result
=
cau
.
doBackup
(
backup_file
.
write
)
if
result
:
backup_path
=
os
.
path
.
join
(
...
...
@@ -782,6 +782,7 @@ def main(argv=None, until=utils.until):
finally
:
sys
.
stderr
.
write
(
'Exiting
\
n
'
)
for
server
in
itertools
.
chain
(
http_list
,
https_list
):
server
.
server_close
()
server
.
shutdown
()
def
manage
(
argv
=
None
):
...
...
@@ -820,7 +821,7 @@ def manage(argv=None):
default
=
[],
metavar
=
'PEM_FILE'
,
action
=
'append'
,
type
=
argparse
.
FileType
(
'r'
),
type
=
argparse
.
FileType
(
'r
b
'
),
help
=
'Import key pairs as initial service CA certificate. '
'May be provided multiple times to import multiple key pairs. '
'Keys and certificates may be in separate files. '
...
...
@@ -846,7 +847,7 @@ def manage(argv=None):
default
=
[],
metavar
=
'PEM_FILE'
,
action
=
'append'
,
type
=
argparse
.
FileType
(
'r'
),
type
=
argparse
.
FileType
(
'r
b
'
),
help
=
'Import service revocation list. Corresponding CA certificate must '
'be already present in the database (including added in the same run '
'using --import-ca).'
,
...
...
@@ -854,7 +855,7 @@ def manage(argv=None):
parser
.
add_argument
(
'--export-ca'
,
metavar
=
'PEM_FILE'
,
type
=
argparse
.
FileType
(
'w'
),
type
=
argparse
.
FileType
(
'w
b
'
),
help
=
'Export all CA certificates in a PEM file. Passphrase will be '
'prompted to protect all keys.'
,
)
...
...
@@ -873,30 +874,35 @@ def manage(argv=None):
# maybe user extracted their private key ?
key_pem
=
utils
.
getKey
(
backup_key_path
)
cau_crt_life_time
=
args
.
user_crt_validity
with
open
(
backup_path
)
as
backup_file
:
with
open
(
backup_crt_path
,
'a'
)
as
new_crt_file
:
new_crt_file
.
write
(
UserCertificateAuthority
.
restoreBackup
(
db_class
=
SQLite3Storage
,
db_path
=
db_path
,
read
=
backup_file
.
read
,
key_pem
=
key_pem
,
csr_pem
=
utils
.
getCertRequest
(
backup_csr_path
),
db_kw
=
{
'table_prefix'
:
'cau'
,
# max_csr_amount: not needed, renewal ignores quota
# Effectively disables certificate expiration
'crt_keep_time'
:
cau_crt_life_time
,
'crt_read_keep_time'
:
cau_crt_life_time
,
'enforce_unique_key_id'
:
True
,
},
kw
=
{
# Disable CA cert renewal
'ca_key_size'
:
None
,
'crt_life_time'
:
cau_crt_life_time
,
},
),
)
with
open
(
backup_path
,
'rb'
,
)
as
backup_file
,
open
(
backup_crt_path
,
'ab'
,
)
as
new_crt_file
:
new_crt_file
.
write
(
UserCertificateAuthority
.
restoreBackup
(
db_class
=
SQLite3Storage
,
db_path
=
db_path
,
read
=
backup_file
.
read
,
key_pem
=
key_pem
,
csr_pem
=
utils
.
getCertRequest
(
backup_csr_path
),
db_kw
=
{
'table_prefix'
:
'cau'
,
# max_csr_amount: not needed, renewal ignores quota
# Effectively disables certificate expiration
'crt_keep_time'
:
cau_crt_life_time
,
'crt_read_keep_time'
:
cau_crt_life_time
,
'enforce_unique_key_id'
:
True
,
},
kw
=
{
# Disable CA cert renewal
'ca_key_size'
:
None
,
'crt_life_time'
:
cau_crt_life_time
,
},
),
)
if
args
.
import_ca
:
import_ca_dict
=
defaultdict
(
(
lambda
:
{
'crt'
:
None
,
'key'
:
None
,
'from'
:
[]}),
...
...
caucase/http_wsgibase.py
View file @
8ce08bf9
...
...
@@ -22,6 +22,7 @@ Separate from .http because of different-licensed code in the middle.
"""
from
__future__
import
absolute_import
from
wsgiref.simple_server
import
ServerHandler
from
.utils
import
toBytes
class
ProxyFile
(
object
):
"""
...
...
@@ -48,7 +49,7 @@ class ChunkedFile(ProxyFile):
"""
Read chunked data.
"""
result
=
''
result
=
b
''
if
not
self
.
_at_eof
:
readline
=
self
.
readline
read
=
self
.
__getattr__
(
'read'
)
...
...
@@ -61,7 +62,7 @@ class ChunkedFile(ProxyFile):
if
len
(
chunk_header
)
>
MAX_CHUNKED_HEADER_LENGTH
:
raise
ValueError
(
'Chunked encoding header too long'
)
try
:
chunk_length
=
int
(
chunk_header
.
split
(
';'
,
1
)[
0
],
16
)
chunk_length
=
int
(
chunk_header
.
split
(
b
';'
,
1
)[
0
],
16
)
except
ValueError
:
raise
ValueError
(
'Invalid chunked encoding header'
)
if
not
chunk_length
:
...
...
@@ -78,7 +79,7 @@ class ChunkedFile(ProxyFile):
if
to_read
!=
chunk_length
:
self
.
_chunk_remaining_length
=
chunk_length
-
to_read
break
if
read
(
2
)
!=
'
\
r
\
n
'
:
if
read
(
2
)
!=
b
'
\
r
\
n
'
:
raise
ValueError
(
'Invalid chunked encoding separator'
)
return
result
...
...
@@ -131,7 +132,7 @@ class CleanServerHandler(ServerHandler):
"""
Emit "100 Continue" intermediate response.
"""
self
.
_write
(
'HTTP/%s 100 Continue
\
r
\
n
\
r
\
n
'
%
(
self
.
http_version
,
self
.
_write
(
b
'HTTP/%s 100 Continue
\
r
\
n
\
r
\
n
'
%
(
toBytes
(
self
.
http_version
)
,
))
self
.
_flush
()
caucase/storage.py
View file @
8ce08bf9
...
...
@@ -25,6 +25,7 @@ import sqlite3
from
threading
import
local
from
time
import
time
from
.exceptions
import
NoStorage
,
NotFound
,
Found
from
.utils
import
toBytes
,
toUnicode
__all__
=
(
'SQLite3Storage'
,
)
...
...
@@ -207,8 +208,8 @@ class SQLite3Storage(local):
)
return
[
{
'crt_pem'
:
x
[
'crt'
].
encode
(
'ascii'
),
'key_pem'
:
x
[
'key'
].
encode
(
'ascii'
),
'crt_pem'
:
toBytes
(
x
[
'crt'
]
),
'key_pem'
:
toBytes
(
x
[
'key'
]
),
}
for
x
in
db
.
cursor
().
execute
(
'SELECT key, crt FROM %sca ORDER BY expiration_date ASC'
%
(
...
...
@@ -326,7 +327,7 @@ class SQLite3Storage(local):
)
if
result
is
None
:
raise
NotFound
return
result
[
'csr'
].
encode
(
'ascii'
)
return
toBytes
(
result
[
'csr'
]
)
def
getCertificateSigningRequestList
(
self
):
"""
...
...
@@ -338,7 +339,11 @@ class SQLite3Storage(local):
return
[
{
'id'
:
str
(
x
[
'id'
]),
'csr'
:
x
[
'csr'
].
encode
(
'ascii'
),
# XXX: because only call chain will end up serialising this value in
# json, and for some reason python3 json module refuses bytes.
# So rather than byte-ify (consistently with all PEM-encoded values)
# to then have to unicode-ify, just unicode-ify here.
'csr'
:
toUnicode
(
x
[
'csr'
]),
}
for
x
in
db
.
cursor
().
execute
(
'SELECT id, csr FROM %scrt WHERE crt IS NULL'
%
(
...
...
@@ -401,7 +406,7 @@ class SQLite3Storage(local):
crt_id
,
)
)
return
row
[
'crt'
].
encode
(
'ascii'
)
return
toBytes
(
row
[
'crt'
]
)
def
getCertificateByKeyIdentifier
(
self
,
key_id
):
"""
...
...
@@ -419,7 +424,7 @@ class SQLite3Storage(local):
)
if
row
is
None
:
raise
NotFound
return
row
[
'crt'
].
encode
(
'ascii'
)
return
toBytes
(
row
[
'crt'
]
)
def
iterCertificates
(
self
):
"""
...
...
@@ -434,7 +439,7 @@ class SQLite3Storage(local):
row
=
c
.
fetchone
()
if
row
is
None
:
break
yield
row
[
'crt'
].
encode
(
'ascii'
)
yield
toBytes
(
row
[
'crt'
]
)
def
revoke
(
self
,
serial
,
expiration_date
):
"""
...
...
@@ -483,7 +488,7 @@ class SQLite3Storage(local):
(
time
(),
)
)
if
row
is
not
None
:
return
row
[
'crl'
].
encode
(
'ascii'
)
return
toBytes
(
row
[
'crl'
]
)
return
None
def
getNextCertificateRevocationListNumber
(
self
):
...
...
@@ -547,7 +552,7 @@ class SQLite3Storage(local):
class (so not limited to table_prefix).
"""
for
statement
in
self
.
_db
.
iterdump
():
yield
statement
.
encode
(
'utf-8'
)
+
'
\
0
'
yield
toBytes
(
statement
,
'utf-8'
)
+
b
'
\
0
'
@
staticmethod
def
restore
(
db_path
,
restorator
):
...
...
@@ -563,14 +568,14 @@ class SQLite3Storage(local):
Produces chunks which correspond (in content, not necessarily in size)
to what dumpIterator produces.
"""
buf
=
''
buf
=
b
''
if
os
.
path
.
exists
(
db_path
):
raise
ValueError
(
'%r exists, not restoring.'
%
(
db_path
,
))
c
=
sqlite3
.
connect
(
db_path
,
isolation_level
=
None
).
cursor
()
for
chunk
in
restorator
:
statement_list
=
(
buf
+
chunk
).
split
(
'
\
0
'
)
statement_list
=
(
buf
+
chunk
).
split
(
b
'
\
0
'
)
buf
=
statement_list
.
pop
()
for
statement
in
statement_list
:
c
.
execute
(
(
statement
).
decode
(
'utf-8'
))
c
.
execute
(
toUnicode
(
statement
,
'utf-8'
))
if
buf
:
raise
ValueError
(
'Short read, backup truncated ?'
)
caucase/test.py
View file @
8ce08bf9
...
...
@@ -22,12 +22,12 @@ Test suite
"""
from
__future__
import
absolute_import
from
Cookie
import
SimpleCookie
from
cStringIO
import
StringIO
import
datetime
import
errno
import
glob
import
HTMLParser
import
httplib
from
io
import
BytesIO
,
StringIO
import
ipaddress
import
json
import
os
...
...
@@ -48,7 +48,9 @@ from cryptography import x509
from
cryptography.hazmat.backends
import
default_backend
from
caucase
import
cli
from
caucase.client
import
CaucaseError
,
CaucaseClient
from
caucase
import
http
# Do not import caucase.http into this namespace: 2to3 will import standard
# http module, which will then be masqued by caucase's http submodule.
import
caucase.http
from
caucase
import
utils
from
caucase
import
exceptions
from
caucase
import
wsgi
...
...
@@ -106,11 +108,13 @@ def canConnect(address): # pragma: no cover
otherwise.
"""
try
:
socket
.
create_connection
(
address
)
sock
=
sock
et
.
create_connection
(
address
)
except
socket
.
error
as
e
:
if
e
.
errno
==
errno
.
ECONNREFUSED
:
return
False
raise
else
:
sock
.
close
()
return
True
def
retry
(
callback
,
try_count
=
200
,
try_delay
=
0.1
):
# pragma: no cover
...
...
@@ -129,7 +133,7 @@ def retry(callback, try_count=200, try_delay=0.1): # pragma: no cover
class
FakeStreamRequest
(
object
):
"""
For testing StreamRequestHandler subclasses
(like http.CaucaseWSGIRequestHandler).
(like
caucase.
http.CaucaseWSGIRequestHandler).
"""
def
__init__
(
self
,
rfile
,
wfile
):
"""
...
...
@@ -144,6 +148,9 @@ class FakeStreamRequest(object):
"""
return
self
.
_rfile
if
'r'
in
mode
else
self
.
_wfile
def
sendall
(
self
,
data
,
flags
=
None
):
# pragma: no cover
self
.
_wfile
.
write
(
data
)
class
NoCloseFileProxy
(
object
):
"""
Intercept .close() calls, for example to allow reading StringIO content
...
...
@@ -324,7 +331,7 @@ class CaucaseTest(unittest.TestCase):
Returns its exit status.
"""
try
:
http
.
manage
(
caucase
.
http
.
manage
(
argv
=
(
'--db'
,
self
.
_server_db
,
'--restore-backup'
,
...
...
@@ -346,7 +353,7 @@ class CaucaseTest(unittest.TestCase):
"""
self
.
_server_until
=
until
=
UntilEvent
(
self
.
_server_event
)
self
.
_server
=
server
=
threading
.
Thread
(
target
=
http
.
main
,
target
=
caucase
.
http
.
main
,
kwargs
=
{
'argv'
:
(
'--db'
,
self
.
_server_db
,
...
...
@@ -453,10 +460,10 @@ class CaucaseTest(unittest.TestCase):
row
=
c
.
fetchone
()
if
row
is
None
:
# pragma: no cover
raise
Exception
(
'CA with serial %r not found'
%
(
serial
,
))
crt
=
utils
.
load_ca_certificate
(
row
[
'crt'
].
encode
(
'ascii'
))
crt
=
utils
.
load_ca_certificate
(
utils
.
toBytes
(
row
[
'crt'
]
))
if
crt
.
serial_number
==
serial
:
new_crt
=
self
.
_setCertificateRemainingLifeTime
(
key
=
utils
.
load_privatekey
(
row
[
'key'
].
encode
(
'ascii'
)),
key
=
utils
.
load_privatekey
(
utils
.
toBytes
(
row
[
'key'
]
)),
crt
=
crt
,
delta
=
delta
,
)
...
...
@@ -489,7 +496,7 @@ class CaucaseTest(unittest.TestCase):
"""
name
=
basename
+
'.key.pem'
assert
not
os
.
path
.
exists
(
name
)
with
open
(
name
,
'w'
)
as
key_file
:
with
open
(
name
,
'w
b
'
)
as
key_file
:
key_file
.
write
(
utils
.
dump_privatekey
(
utils
.
generatePrivateKey
(
key_len
=
key_len
),
))
...
...
@@ -516,7 +523,7 @@ class CaucaseTest(unittest.TestCase):
"""
name
=
basename
+
'.csr.pem'
assert
not
os
.
path
.
exists
(
name
)
with
open
(
name
,
'w'
)
as
csr_file
:
with
open
(
name
,
'w
b
'
)
as
csr_file
:
csr_file
.
write
(
utils
.
dump_certificate_request
(
csr_builder
.
sign
(
...
...
@@ -604,7 +611,8 @@ class CaucaseTest(unittest.TestCase):
'--mode'
,
mode
,
'--get-csr'
,
csr_id
,
csr2_path
,
)
self
.
assertEqual
(
open
(
csr_path
).
read
(),
open
(
csr2_path
).
read
())
with
open
(
csr_path
,
'rb'
)
as
csr_file
,
open
(
csr2_path
,
'rb'
)
as
csr2_file
:
self
.
assertEqual
(
csr_file
.
read
(),
csr2_file
.
read
())
# Sign using user cert
# Note: assuming user does not know the csr_id and keeps their own copy of
# issued certificates.
...
...
@@ -1143,12 +1151,14 @@ class CaucaseTest(unittest.TestCase):
# Check renewed CRT filtering does not alter clean signed certificate
# content (especially, caucase auto-signed flag must not appear).
before_key
=
open
(
key_path
).
read
()
with
open
(
key_path
,
'rb'
)
as
key_file
:
before_key
=
key_file
.
read
()
self
.
_runClient
(
'--threshold'
,
'100'
,
'--renew-crt'
,
key_path
,
''
,
)
after_key
=
open
(
key_path
).
read
()
with
open
(
key_path
,
'rb'
)
as
key_file
:
after_key
=
key_file
.
read
()
assert
before_key
!=
after_key
checkCRT
(
key_path
)
...
...
@@ -1215,7 +1225,7 @@ class CaucaseTest(unittest.TestCase):
)
# As we will use this crt as trust anchor, we must make the client believe
# it knew it all along.
with
open
(
self
.
_client_user_ca_crt
,
'w'
)
as
client_user_ca_crt_file
:
with
open
(
self
.
_client_user_ca_crt
,
'w
b
'
)
as
client_user_ca_crt_file
:
client_user_ca_crt_file
.
write
(
new_cau_crt_pem
)
self
.
_startServer
()
new_user_key
=
self
.
_createAndApproveCertificate
(
...
...
@@ -1302,11 +1312,11 @@ class CaucaseTest(unittest.TestCase):
self
.
_server_key
,
crl
=
None
,
)
with
open
(
self
.
_server_key
,
'w'
)
as
server_key_file
:
with
open
(
self
.
_server_key
,
'w
b
'
)
as
server_key_file
:
server_key_file
.
write
(
key_pem
)
server_key_file
.
write
(
utils
.
dump_certificate
(
self
.
_setCertificateRemainingLifeTime
(
key
=
utils
.
load_privatekey
(
http_cas_key
.
encode
(
'ascii'
)),
key
=
utils
.
load_privatekey
(
utils
.
toBytes
(
http_cas_key
)),
crt
=
utils
.
load_certificate
(
crt_pem
,
[
...
...
@@ -1318,10 +1328,13 @@ class CaucaseTest(unittest.TestCase):
)
))
server_key_file
.
write
(
ca_crt_pem
)
reference_server_key
=
open
(
self
.
_server_key
).
read
()
def
readServerKey
():
with
open
(
self
.
_server_key
,
'rb'
)
as
server_key_file
:
return
server_key_file
.
read
()
reference_server_key
=
readServerKey
()
self
.
_startServer
()
if
not
retry
(
lambda
:
open
(
self
.
_server_key
).
read
()
!=
reference_server_key
,
lambda
:
readServerKey
()
!=
reference_server_key
,
):
# pragma: no cover
raise
AssertionError
(
'Server did not renew its key pair within 1 second'
)
# But user still trusts the server
...
...
@@ -1363,7 +1376,8 @@ class CaucaseTest(unittest.TestCase):
utils
.
load_ca_certificate
(
x
)
for
x
in
utils
.
getCertList
(
self
.
_client_user_ca_crt
)
]
cau_crl
=
open
(
self
.
_client_user_crl
).
read
()
with
open
(
self
.
_client_user_crl
,
'rb'
)
as
client_user_crl_file
:
cau_crl
=
client_user_crl_file
.
read
()
class
DummyCAU
(
object
):
"""
Mock CAU.
...
...
@@ -1382,7 +1396,7 @@ class CaucaseTest(unittest.TestCase):
"""
Return a dummy string as CA certificate
"""
return
'notreallyPEM'
return
b
'notreallyPEM'
@
staticmethod
def
getCertificateRevocationList
():
...
...
@@ -1441,7 +1455,7 @@ class CaucaseTest(unittest.TestCase):
if
key
in
header_dict
:
# pragma: no cover
value
=
header_dict
[
key
]
+
','
+
value
header_dict
[
key
]
=
value
return
int
(
status
),
reason
,
header_dict
,
''
.
join
(
body
)
return
int
(
status
),
reason
,
header_dict
,
b
''
.
join
(
body
)
UNAUTHORISED_STATUS
=
401
HATEOAS_HTTP_PREFIX
=
u"http://caucase.example.com:8000/base/path"
...
...
@@ -1841,7 +1855,7 @@ class CaucaseTest(unittest.TestCase):
header_dict
[
'Content-Security-Policy'
],
"frame-ancestors 'none'"
,
)
assertHTMLNoScriptAlert
(
body
)
assertHTMLNoScriptAlert
(
utils
.
toUnicode
(
body
)
)
# POST /cors sets cookie
def
getCORSPostEnvironment
(
kw
=
(),
input_dict
=
(
(
'return_to'
,
return_url
),
...
...
@@ -2042,9 +2056,9 @@ class CaucaseTest(unittest.TestCase):
table_prefix
=
'cau'
,
).
dumpIterator
())
CRL_INSERT
=
'INSERT INTO "caucrl" '
CRT_INSERT
=
'INSERT INTO "caucrt" '
REV_INSERT
=
'INSERT INTO "caurevoked" '
CRL_INSERT
=
b
'INSERT INTO "caucrl" '
CRT_INSERT
=
b
'INSERT INTO "caucrt" '
REV_INSERT
=
b
'INSERT INTO "caurevoked" '
def
filterBackup
(
backup
,
expect_rev
):
"""
Remove all lines which are know to differ between original batabase and
...
...
@@ -2145,7 +2159,7 @@ class CaucaseTest(unittest.TestCase):
user2_newnew_key_path
,
)
user2_new_bare_key_path
=
user2_new_key_path
+
'.bare_key'
with
open
(
user2_new_bare_key_path
,
'w'
)
as
bare_key_file
:
with
open
(
user2_new_bare_key_path
,
'w
b
'
)
as
bare_key_file
:
bare_key_file
.
write
(
utils
.
getKeyPair
(
user2_new_key_path
)[
1
])
self
.
assertEqual
(
self
.
_restoreServer
(
...
...
@@ -2174,13 +2188,13 @@ class CaucaseTest(unittest.TestCase):
'--revoke-crt'
,
service_key
,
service_key
,
)
self
.
_runClient
()
getBytePass_orig
=
http
.
getBytePass
getBytePass_orig
=
caucase
.
http
.
getBytePass
orig_stdout
=
sys
.
stdout
try
:
http
.
getBytePass
=
lambda
x
:
'test'
caucase
.
http
.
getBytePass
=
lambda
x
:
b
'test'
sys
.
stdout
=
stdout
=
StringIO
()
self
.
assertFalse
(
os
.
path
.
exists
(
exported_ca
),
exported_ca
)
http
.
manage
(
caucase
.
http
.
manage
(
argv
=
(
'--db'
,
self
.
_server_db
,
'--export-ca'
,
exported_ca
,
...
...
@@ -2189,7 +2203,7 @@ class CaucaseTest(unittest.TestCase):
self
.
assertTrue
(
os
.
path
.
exists
(
exported_ca
),
exported_ca
)
server_db2
=
self
.
_server_db
+
'2'
self
.
assertFalse
(
os
.
path
.
exists
(
server_db2
),
server_db2
)
http
.
manage
(
caucase
.
http
.
manage
(
argv
=
(
'--db'
,
server_db2
,
'--import-ca'
,
exported_ca
,
...
...
@@ -2208,7 +2222,7 @@ class CaucaseTest(unittest.TestCase):
)
finally
:
sys
.
stdout
=
orig_stdout
http
.
getBytePass
=
getBytePass_orig
caucase
.
http
.
getBytePass
=
getBytePass_orig
def
testWSGIBase
(
self
):
"""
...
...
@@ -2220,10 +2234,10 @@ class CaucaseTest(unittest.TestCase):
"""
Trigger execution of app, with given request.
"""
wfile
=
String
IO
()
http
.
CaucaseWSGIRequestHandler
(
wfile
=
Bytes
IO
()
caucase
.
http
.
CaucaseWSGIRequestHandler
(
FakeStreamRequest
(
StringIO
(
'
\
r
\
n
'
.
join
(
request_line_list
+
[
''
])),
BytesIO
(
b'
\
r
\
n
'
.
join
(
request_line_list
+
[
b
''
])),
NoCloseFileProxy
(
wfile
),
),
(
'0.0.0.0'
,
0
),
...
...
@@ -2235,26 +2249,28 @@ class CaucaseTest(unittest.TestCase):
"""
Naive extraction of http status out of an http response.
"""
_
,
code
,
_
=
response_line_list
[
0
].
split
(
' '
,
2
)
_
,
code
,
_
=
response_line_list
[
0
].
split
(
b
' '
,
2
)
return
int
(
code
)
def
getBody
(
response_line_list
):
"""
Naive extraction of http response body.
"""
return
'
\
r
\
n
'
.
join
(
response_line_list
[
response_line_list
.
index
(
''
)
+
1
:])
return
b'
\
r
\
n
'
.
join
(
response_line_list
[
response_line_list
.
index
(
b''
)
+
1
:],
)
self
.
assertEqual
(
getStatus
(
run
([
'GET /'
+
'a'
*
65537
])),
getStatus
(
run
([
b'GET /'
+
b
'a'
*
65537
])),
414
,
)
expect_continue_request
=
[
'PUT / HTTP/1.1'
,
'Expect: 100-continue'
,
'Content-Length: 4'
,
'Content-Type: text/plain'
,
''
,
'Test'
,
b
'PUT / HTTP/1.1'
,
b
'Expect: 100-continue'
,
b
'Content-Length: 4'
,
b
'Content-Type: text/plain'
,
b
''
,
b
'Test'
,
]
# No read: 200 OK
self
.
assertEqual
(
...
...
@@ -2271,7 +2287,7 @@ class CaucaseTest(unittest.TestCase):
self
.
assertEqual
(
getStatus
(
run
(
[
'PUT / HTTP/1.0'
,
b
'PUT / HTTP/1.0'
,
]
+
expect_continue_request
[
1
:],
read_app
,
)),
...
...
@@ -2279,19 +2295,19 @@ class CaucaseTest(unittest.TestCase):
)
chunked_request
=
[
'PUT / HTTP/1.1'
,
'Transfer-Encoding: chunked'
,
''
,
'f;some=extension'
,
'123456789abcd
\
r
\
n
'
,
'3'
,
'ef0'
,
'0'
,
'X-Chunked-Trailer: blah'
b
'PUT / HTTP/1.1'
,
b
'Transfer-Encoding: chunked'
,
b
''
,
b
'f;some=extension'
,
b
'123456789abcd
\
r
\
n
'
,
b
'3'
,
b
'ef0'
,
b
'0'
,
b
'X-Chunked-Trailer: blah'
]
self
.
assertEqual
(
getBody
(
run
(
chunked_request
,
read_app
)),
'123456789abcd
\
r
\
n
ef0'
,
b
'123456789abcd
\
r
\
n
ef0'
,
)
self
.
assertEqual
(
getBody
(
run
(
...
...
@@ -2300,7 +2316,7 @@ class CaucaseTest(unittest.TestCase):
environ
[
'wsgi.input'
].
read
(),
environ
[
'wsgi.input'
].
read
(),
]))),
'123456789abcd
\
r
\
n
ef0'
,
b
'123456789abcd
\
r
\
n
ef0'
,
)
self
.
assertEqual
(
getBody
(
run
(
...
...
@@ -2309,7 +2325,7 @@ class CaucaseTest(unittest.TestCase):
environ
[
'wsgi.input'
].
read
(
6
),
environ
[
'wsgi.input'
].
read
(),
]))),
'123456789abcd
\
r
\
n
ef0'
,
b
'123456789abcd
\
r
\
n
ef0'
,
)
self
.
assertEqual
(
getBody
(
run
(
...
...
@@ -2317,44 +2333,44 @@ class CaucaseTest(unittest.TestCase):
DummyApp
(
lambda
environ
:
[
environ
[
'wsgi.input'
].
read
(
32
),
]))),
'123456789abcd
\
r
\
n
ef0'
,
b
'123456789abcd
\
r
\
n
ef0'
,
)
self
.
assertEqual
(
getStatus
(
run
([
'PUT / HTTP/1.1'
,
'Transfer-Encoding: chunked'
,
''
,
'1'
,
'abc'
,
# Chunk longer than advertised in header.
b
'PUT / HTTP/1.1'
,
b
'Transfer-Encoding: chunked'
,
b
''
,
b
'1'
,
b
'abc'
,
# Chunk longer than advertised in header.
],
read_app
)),
500
,
)
self
.
assertEqual
(
getStatus
(
run
([
'PUT / HTTP/1.1'
,
'Transfer-Encoding: chunked'
,
''
,
'y'
,
# Not a valid chunk header
b
'PUT / HTTP/1.1'
,
b
'Transfer-Encoding: chunked'
,
b
''
,
b
'y'
,
# Not a valid chunk header
],
read_app
)),
500
,
)
self
.
assertEqual
(
getStatus
(
run
([
'PUT / HTTP/1.1'
,
'Transfer-Encoding: chunked'
,
''
,
'f;'
+
'a'
*
65537
,
# header too long
b
'PUT / HTTP/1.1'
,
b
'Transfer-Encoding: chunked'
,
b
''
,
b'f;'
+
b
'a'
*
65537
,
# header too long
],
read_app
)),
500
,
)
self
.
assertEqual
(
getStatus
(
run
([
'PUT / HTTP/1.1'
,
'Transfer-Encoding: chunked'
,
''
,
'0'
,
'a'
*
65537
,
# trailer too long
b
'PUT / HTTP/1.1'
,
b
'Transfer-Encoding: chunked'
,
b
''
,
b
'0'
,
b
'a'
*
65537
,
# trailer too long
],
read_app
)),
500
,
)
...
...
@@ -2580,5 +2596,10 @@ class CaucaseTest(unittest.TestCase):
self
.
assertEqual
(
os
.
stat
(
self
.
_server_db
).
st_mode
&
0o777
,
0o600
)
self
.
assertEqual
(
os
.
stat
(
self
.
_server_key
).
st_mode
&
0o777
,
0o600
)
if
getattr
(
CaucaseTest
,
'assertItemsEqual'
,
None
)
is
None
:
# Because python3 decided it should be named differently, and 2to3 cannot
# pick it up, and this code must remain python2-compatible... Yay !
CaucaseTest
.
assertItemsEqual
=
CaucaseTest
.
assertCountEqual
if
__name__
==
'__main__'
:
# pragma: no cover
unittest
.
main
()
caucase/utils.py
View file @
8ce08bf9
...
...
@@ -21,6 +21,7 @@ Caucase - Certificate Authority for Users, Certificate Authority for SErvices
Small-ish functions needed in many places.
"""
from
__future__
import
absolute_import
from
binascii
import
a2b_base64
,
b2a_base64
from
collections
import
defaultdict
import
datetime
import
json
...
...
@@ -273,19 +274,20 @@ def wrap(payload, key, digest):
"""
Sign payload (which gets json-serialised) with key, using given digest.
"""
payload
=
json
.
dumps
(
payload
).
encode
(
'utf-8'
)
payload
=
toBytes
(
json
.
dumps
(
payload
),
'utf-8'
)
hash_class
=
getattr
(
hashes
,
digest
.
upper
())
return
{
'payload'
:
payload
,
'payload'
:
toUnicode
(
payload
)
,
'digest'
:
digest
,
'signature'
:
key
.
sign
(
payload
+
digest
+
' '
,
# For some reason, python3 thinks that a b2a method should return bytes.
'signature'
:
toUnicode
(
b2a_base64
(
key
.
sign
(
payload
+
toBytes
(
digest
)
+
b' '
,
padding
.
PSS
(
mgf
=
padding
.
MGF1
(
hash_class
()),
salt_length
=
padding
.
PSS
.
MAX_LENGTH
,
),
hash_class
(),
)
.
encode
(
'base64'
),
)
)
),
}
def
nullWrap
(
payload
):
...
...
@@ -308,10 +310,10 @@ def unwrap(wrapped, getCertificate, digest_list):
Note: does *not* verify received certificate itself (validity, issuer, ...).
"""
# Check whether given digest is allowed
digest
=
wrapped
[
'digest'
]
.
encode
(
'ascii'
)
digest
=
wrapped
[
'digest'
]
if
digest
not
in
digest_list
:
raise
cryptography
.
exceptions
.
UnsupportedAlgorithm
(
'%r is not in allowed digest list
'
,
'%r is not in allowed digest list
%r'
%
(
digest
,
digest_list
)
,
)
hash_class
=
getattr
(
hashes
,
digest
.
upper
())
try
:
...
...
@@ -319,11 +321,11 @@ def unwrap(wrapped, getCertificate, digest_list):
except
ValueError
:
raise
NotJSON
x509
.
load_pem_x509_certificate
(
getCertificate
(
payload
).
encode
(
'ascii'
),
toBytes
(
getCertificate
(
payload
)
),
_cryptography_backend
,
).
public_key
().
verify
(
wrapped
[
'signature'
].
encode
(
'ascii'
).
decode
(
'base64'
),
wrapped
[
'payload'
].
encode
(
'utf-8'
)
+
digest
+
' '
,
a2b_base64
(
toBytes
(
wrapped
[
'signature'
])
),
toBytes
(
wrapped
[
'payload'
],
'utf-8'
)
+
toBytes
(
digest
)
+
b
' '
,
padding
.
PSS
(
mgf
=
padding
.
MGF1
(
hash_class
()),
salt_length
=
padding
.
PSS
.
MAX_LENGTH
,
...
...
@@ -445,6 +447,18 @@ class SleepInterrupt(KeyboardInterrupt):
"""
pass
def
toUnicode
(
value
,
encoding
=
'ascii'
):
"""
Convert value to unicode object, if it is not already.
"""
return
value
if
isinstance
(
value
,
unicode
)
else
value
.
decode
(
encoding
)
def
toBytes
(
value
,
encoding
=
'ascii'
):
"""
Convert valye to bytes object, if it is not already.
"""
return
value
if
isinstance
(
value
,
bytes
)
else
value
.
encode
(
encoding
)
def
interruptibleSleep
(
duration
):
# pragma: no cover
"""
Like sleep, but raises SleepInterrupt when interrupted by KeyboardInterrupt
...
...
caucase/wsgi.py
View file @
8ce08bf9
...
...
@@ -19,11 +19,11 @@
Caucase - Certificate Authority for Users, Certificate Authority for SErvices
"""
from
__future__
import
absolute_import
from
cgi
import
escape
from
Cookie
import
SimpleCookie
,
CookieError
import
httplib
import
json
import
os
import
sys
import
threading
import
time
import
traceback
...
...
@@ -34,10 +34,15 @@ import jwt
from
.
import
utils
from
.
import
exceptions
if
sys
.
version_info
>=
(
3
,
):
# pragma: no cover
from
html
import
escape
else
:
# pragma: no cover
from
cgi
import
escape
__all__
=
(
'Application'
,
'CORSTokenManager'
)
# TODO: l10n
CORS_FORM_TEMPLATE
=
'''
\
CORS_FORM_TEMPLATE
=
b
'''
\
<html>
<head>
<title>Caucase CORS access</title>
...
...
@@ -213,11 +218,11 @@ class CORSTokenManager(object):
key
=
os
.
urandom
(
32
)
secret_list
.
append
((
now
+
self
.
_secret_validity_period
,
key
))
self
.
_onNewKey
(
secret_list
)
return
jwt
.
encode
(
return
utils
.
toUnicode
(
jwt
.
encode
(
payload
=
{
'p'
:
payload
},
key
=
key
,
algorithm
=
'HS256'
,
)
)
)
def
verify
(
self
,
token
,
default
=
None
):
"""
...
...
@@ -571,7 +576,7 @@ class Application(object):
except
exceptions
.
NoStorage
:
raise
InsufficientStorage
except
exceptions
.
NotJSON
:
raise
BadRequest
(
'Invalid json payload'
)
raise
BadRequest
(
b
'Invalid json payload'
)
except
exceptions
.
CertificateAuthorityException
as
e
:
raise
BadRequest
(
str
(
e
))
except
Exception
:
...
...
@@ -581,7 +586,7 @@ class Application(object):
except
ApplicationError
as
e
:
status
=
e
.
status
header_list
=
e
.
response_headers
result
=
[
str
(
x
)
for
x
in
e
.
args
]
result
=
[
utils
.
toBytes
(
str
(
x
)
)
for
x
in
e
.
args
]
# Note: header_list and cors_header_list are expected to contain
# distinct header sets. This may not always stay true for "Vary".
header_list
.
extend
(
cors_header_list
)
...
...
@@ -605,7 +610,7 @@ class Application(object):
try
:
return
int
(
crt_id
)
except
ValueError
:
raise
BadRequest
(
'Invalid integer'
)
raise
BadRequest
(
b
'Invalid integer'
)
@
staticmethod
def
_read
(
environ
):
...
...
@@ -619,9 +624,9 @@ class Application(object):
try
:
length
=
int
(
environ
.
get
(
'CONTENT_LENGTH'
)
or
MAX_BODY_LENGTH
)
except
ValueError
:
raise
BadRequest
(
'Invalid Content-Length'
)
raise
BadRequest
(
b
'Invalid Content-Length'
)
if
length
>
MAX_BODY_LENGTH
:
raise
TooLarge
(
'Content-Length limit exceeded'
)
raise
TooLarge
(
b
'Content-Length limit exceeded'
)
return
environ
[
'wsgi.input'
].
read
(
length
)
def
_authenticate
(
self
,
environ
,
header_list
):
...
...
@@ -653,12 +658,12 @@ class Application(object):
json decoding fails.
"""
if
environ
.
get
(
'CONTENT_TYPE'
)
!=
'application/json'
:
raise
BadRequest
(
'Bad Content-Type'
)
raise
BadRequest
(
b
'Bad Content-Type'
)
data
=
self
.
_read
(
environ
)
try
:
return
json
.
loads
(
data
)
except
ValueError
:
raise
BadRequest
(
'Invalid json'
)
raise
BadRequest
(
b
'Invalid json'
)
def
_createCORSCookie
(
self
,
environ
,
value
):
"""
...
...
@@ -859,7 +864,10 @@ class Application(object):
name
=
action
[
'name'
]
assert
name
not
in
hal_section_dict
,
name
hal_section_dict
[
name
]
=
descriptor_dict
return
self
.
_returnFile
(
json
.
dumps
(
hal
),
'application/hal+json'
)
return
self
.
_returnFile
(
utils
.
toBytes
(
json
.
dumps
(
hal
)),
'application/hal+json'
,
)
def
getCORSForm
(
self
,
context
,
environ
):
# pylint: disable=unused-argument
"""
...
...
@@ -881,9 +889,9 @@ class Application(object):
raise
BadRequest
return
self
.
_returnFile
(
CORS_FORM_TEMPLATE
%
{
'caucase'
:
escape
(
self
.
_http_url
,
quote
=
True
),
'return_to'
:
escape
(
return_to
,
quote
=
True
),
'origin'
:
escape
(
origin
,
quote
=
True
),
b'caucase'
:
utils
.
toBytes
(
escape
(
self
.
_http_url
,
quote
=
True
)
),
b'return_to'
:
utils
.
toBytes
(
escape
(
return_to
,
quote
=
True
)
),
b'origin'
:
utils
.
toBytes
(
escape
(
origin
,
quote
=
True
)
),
},
'text/html'
,
[
...
...
@@ -902,7 +910,7 @@ class Application(object):
if
environ
[
'wsgi.url_scheme'
]
!=
'https'
:
raise
NotFound
if
environ
.
get
(
'CONTENT_TYPE'
)
!=
'application/x-www-form-urlencoded'
:
raise
BadRequest
(
'Unhandled Content-Type'
)
raise
BadRequest
(
b
'Unhandled Content-Type'
)
try
:
form_dict
=
parse_qs
(
self
.
_read
(
environ
),
strict_parsing
=
True
)
origin
,
=
form_dict
[
'origin'
]
...
...
@@ -961,7 +969,7 @@ class Application(object):
header_list
=
[]
self
.
_authenticate
(
environ
,
header_list
)
return
self
.
_returnFile
(
json
.
dumps
(
context
.
getCertificateRequestList
(
)),
utils
.
toBytes
(
json
.
dumps
(
context
.
getCertificateRequestList
()
)),
'application/json'
,
header_list
,
)
...
...
@@ -973,7 +981,7 @@ class Application(object):
try
:
csr_id
=
context
.
appendCertificateSigningRequest
(
self
.
_read
(
environ
))
except
exceptions
.
NotACertificateSigningRequest
:
raise
BadRequest
(
'Not a valid certificate signing request'
)
raise
BadRequest
(
b
'Not a valid certificate signing request'
)
return
(
STATUS_CREATED
,
[(
'Location'
,
str
(
csr_id
))],
[])
def
deletePendingCertificateRequest
(
self
,
context
,
environ
,
subpath
):
...
...
@@ -1013,7 +1021,7 @@ class Application(object):
Handle GET /{context}/crt/ca.crt.json urls.
"""
return
self
.
_returnFile
(
json
.
dumps
(
context
.
getValidCACertificateChain
(
)),
utils
.
toBytes
(
json
.
dumps
(
context
.
getValidCACertificateChain
()
)),
'application/json'
,
)
...
...
@@ -1050,7 +1058,7 @@ class Application(object):
context
.
digest_list
,
)
context
.
revoke
(
crt_pem
=
payload
[
'revoke_crt_pem'
].
encode
(
'ascii'
),
crt_pem
=
utils
.
toBytes
(
payload
[
'revoke_crt_pem'
]
),
)
return
(
STATUS_NO_CONTENT
,
header_list
,
[])
...
...
@@ -1065,8 +1073,8 @@ class Application(object):
)
return
self
.
_returnFile
(
context
.
renew
(
crt_pem
=
payload
[
'crt_pem'
].
encode
(
'ascii'
),
csr_pem
=
payload
[
'renew_csr_pem'
].
encode
(
'ascii'
),
crt_pem
=
utils
.
toBytes
(
payload
[
'crt_pem'
]
),
csr_pem
=
utils
.
toBytes
(
payload
[
'renew_csr_pem'
]
),
),
'application/pkix-cert'
,
)
...
...
@@ -1084,7 +1092,7 @@ class Application(object):
elif
environ
.
get
(
'CONTENT_TYPE'
)
==
'application/pkcs10'
:
template_csr
=
utils
.
load_certificate_request
(
body
)
else
:
raise
BadRequest
(
'Bad Content-Type'
)
raise
BadRequest
(
b
'Bad Content-Type'
)
header_list
=
[]
self
.
_authenticate
(
environ
,
header_list
)
context
.
createCertificate
(
...
...
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