Compressed Signature and Validation

Allows for a new form of document signature.

pkiz_sign will take data and encode it in a string that starts with
the substring  "PKIZ_".  This prefix indicates that the data has been:
1) Signed via PKI in Crypto Message Syntax (CMS) in binary (DER) format
2) Compressed using zlib (comparable to gzip)
3) urlsafe-base64 decoded

This process is reversed to validate the data.

middleware/auth_token.py will be capable of validating Keystone
tokens that are marshalled in the new format.  The current existing
"PKI" tokens will continue to be identified with "MII", issued by
default, and validated as well.  It will require corresponding changes
on the Keystone server to issue the new token format.

A separate script for generating the sample
data used in the unit tests,
examples/pki/gen_cmsz.py,
also serves as an example of how to
call the API from Python code.

Some of the sample data for the old tests had to be regenerated. A
stray comma in one of the JSON files made for non-parsing JSON.

Blueprint: compress-tokens
Closes-Bug: #1255321

Change-Id: Ia9a66ba3742da0bcd58c4c096b28cc8a66ad6569
This commit is contained in:
Adam Young 2014-02-04 20:43:07 -05:00 committed by Morgan Fainberg
parent a95edc7f38
commit 3d6d749e6f
20 changed files with 412 additions and 59 deletions

View File

@ -0,0 +1 @@
PKIZ_eJylVtly4jgUfddXzHuqK9jGED_Mgze8BInYeEF-8wJeBYTF29ePbEh3p9OZycxQRZUtS_eee87Rlb59oz9J1Qz0hwzXw8s3AA1DZxpsPh8CI6tjJFqxfKBjnSLL0pMli5bayo6oS6l7UlIoawUd31qavH7V1kbEAcVSdTGkg4mrpunG3nZmhllUxRzMV7k0N_b0eR8cMespeGNnkSbsjeKQ-tw5j8jiAoK1MTNkk43Ylol8N1_KYh74fBlrwjHa2_3bZOzbl9DnPbdsaGAxD3V7EiuHGix7tUPdtFkW4hU6hynqY3bJ4XbZ4wkuAgLZIMcsZGBv9ch3p9jBTUAQWSlVjgvMAugkmZE3qbE3q4Ct6igfEXWBnxwjln-JyA0VzT4JNuYV--07FGCA8X9QgAHGDxQSg0l7xIy3duQRySHR7WaVP9XQMbgxgTxtV0XKoR7XSaHWABV2jgjuA2IWuHd7pEAmcLIMFRLBLJ6ufDNHBW4Rq-Y7b3KmQSfbjVQN5Br7oAaR7l2oEsOHKiJ2E7HVNdHRLtKqa3iTMtps6EL9JttdtX2kLa6YdXPwb2X7hS8ewKLsBsL-qxLgs8jvA39OLnjPbtmtHGNg9yNhpLpgP6nGgMS7BrpUD4hAzAhn-nCKOxp5cUl26yal-4HCZO4L-Toh6qcWB18kazDXZDQX1f5n6cE_aT9kjom3D33hetP-TnQpXAf5Aa1zgFTFhM-ixVccaA0cXeH6iUWawYKgoGAIKpADJ7D3qpWmslALiqBIeUwMFhUqh29GaxLfpHyhL22m39b7u3LB33qdoDraSEyifWw0G7Y9RuTSg1EOhhGWMm1fAw-0K43wWI-PObt-c-FndgdfkLCn_DCoE1iYT5tfLT-osP5q9_ldcPAx-lebittARaxBUhh0wBQ262GxzcfanQPfrmi9x0QvPyVw4AIMBN4X15S40W10L1RbXTpSB46TjMJoYJ9eoKJeoJO5sFBn0LFmUElCcINNs5HFNRkg085Ds2W0jCoY3-0u8d1B3h8b7G3-QriCYRDenFYGG1TEpGoS7d5UNJ6JtGb4dgxufEyG4LSMXehbrbGf3PbC_WND-1wR-FkdaXRv5KYw1J5s6NGW35DFRDjTJO_6JaCa0gXuW0sbnjujmvwC2awSIpwC396NAW-GG9fcA3j9zwfmvfN29Lyk5ZkfXDoicYzR-kMJTMx63c8Lg00wKFJuOK-_Geo7T2_lfp8D7pPupDDCztFkMT40aaprYqpK0NBK-t9C69DIIlY8y1qojcpA69zIFlYAHdDUxvTcXl1CsdRExlVlCcrWRG3VQrSkFHmSGDuyh5iI8HxCFhS-uoaSOM4FcgZNh5OqqEIT7KMTtNVGacZMS7XJlsGm6hONti9HraAMv99M6MXEFG3sgx_b1hOjIdD-FmhJhC7oVRdKxphJbOHSZb1zkEtO6CfXwKfXH5oMSA1ePDdTRcwOjWL9fFdSJckS6bVHFfF1IvDP-CWbCmXy9NpVu_BpqcRivc16oLGr4hK_vmoz1BDkvSxetosqVk-l6J5X-elhpsFty70GHNfuNX6VQnbGwedWP0pnp9wFMTBTn1wV_hryDJ7He69j2piEh31eh4yyeDTnVnOUqwekOJskWmXPiGm6R-UlY4xz-ZjMe0C6bus-TBfLy45cLuHM19gyW1Df1s5JbjUu1XU3FphSW7XS6UnvrDYL42XW7YvwyD-fOhBCxpuHZbEsrSeTeY6cR3W5TY66RQ4MmmvZUYXRflFI5uuWEecPjMA9If-BMIFQZVOb04E_O0ai7my7iTy3iyjLPXa6O678kDwyBSTepGIrln2AO_U4mzlzS-TU7WP1_DJr_vwTjHdVFSk_7q1_AfJ_mjc=

View File

@ -0,0 +1 @@
PKIZ_eJylVst2ozgU3OsrZp_Tx4CNY5biaRFLGMx7ZyDGYMBObJ5fPwInpyedzkxmhhUIqVS36t4r_fhBH1HREPlDwrvx4wfACK1bM9CfziE6NjGBZiyd6dg1lyRxuZCgqXSSDddi6rzKKZa0cTxeaNLuRduhaA5kU1nDPR2MVkqaeo_PvX4MOFLEc5wZmfiIKvpehZeAc-XAt46RJlQoP6fe_JpFpXoD4Q4tkaRzEdexkedkGwlmocefYk24RJU1vE8OPOu293jXObUUGGb7tcXE8rkBm0HpSb9oNzmssX1ekCHmNvOg2wwBE-RhibkwCzjM4sEciOcsAjtow5KUhlxkQR5wANvJEWVtiiq9CLmiibKJUR96ySXi-G1U3lnR3ZnQ1-vA6z6wACON_8MCjDR-shDZoOwuAevubGlick7WVmtkqwbbaD5tIC06I0_nZAiaJFcaQHIrI2UwhKWeB4MzEBmzoX08klwsAy5YGJ6ekTzoCKdkB5e5UlDm2ReLUVxUhQ2I1u6NOjH-KKLSaiOuqJM1OURaUe_vVka-Txeu77a9uVZFmloHnJOBf2vbL3rxAOenfhTsvzoBvkL-CPy1uOCjuqfesNGo7mfByuIWeEkxAZZuHa7FZmQEYla40pfXuKfI6i057NqU1gOlyb4t5JukVL5McfBNscbkYqbkot7_1XrwT96PO8elW-09ob57_yb0SahH-wGNc6RUxCV_jNTvZKA5alTj3YojGuJAmFMyJcmJjRk8uIWhKRzWwjzMUz4oEUdyZR7cE61NPJ3qRb5VTL-N93fhgr_N9ZI0kS-yifa50fhcd4nK2wAmO1hW2Ei0fY060K400eNcPp5bzXsWfpXu4BsWDlQflvQCh7NF-2vKjy7svtt9fgcOPqN_t6k4LZZhA5Ic0QFd8HfjYouPtTcNPKug8V6S9elLAUctwCjg2-KGCjdlG62F4nktXmgGTpNQjlo8pDcsKzdsHx2cK0tsm0ssJ3twp013K9U6GSnTzkN3O9IwinD6tvrEc0Z7fxbY-3xVqME4iO-Zdgp9ksdl0SbaW1PReDbS2vHfJbzrwYzgNIzD3jM7VDH3Wnj72dI-l4fesYk0WhuZLoyxJz492rI7s7gUrnSTD_0SUE_pAue9pY3vPSqYXyi7A7X1MDVV-71CRzCcgRHlQwN5B6w-deKenp8Fzt4dm0DvGny1C41zsnQKoxAuoUzrxWcFHCCxp8c8jAMJ0PO_Tfdmm4aLTsohElPiitCxoe100gD1-3dgw8K1sXltJTOQXdNESqvLpq3sABahBllHETusO3O3jqqCoylcYAu1CpwmPyltsY01t3bmFr07XDvFhts78NUGknIrnn3C0Fqgdjotav96WzmJ6jF8Df1iSDTawhyxGYHiO1AdzfUKYMtslXTaSVbamx16XYlUcgkpYEgjUj5cbyAR09PL8ZRpQsuINHwVQLij9yBp74o5-3C9beMjRm4RGubu5K2F9HGJocPh_HJ7OM-zk36Nb-eHw2sxnGZ74rvrAqi2wSpx1jJyNWd7CHM1LftoqJiSh-nGUy32Js_OzhI1jmuXPJJmF9hh5aytDpquHbdgGGbIvIVPr71BcFdDy7fk2ZFJ92m33szIIMlu-IIEf-UzJFJOwolZRZ1hz-ONETD7_AwstzFmO7fpltxy63KH5wd0qXbBIt7HrOs-YWgF-_PT7CF9KnouPykraZg9YN1WOdW_7O0ckPm5UMNs268OL8QpD24qFNvu8eHFEjtI2uct79Qmn3P8cWWacap2kXw1ZCHP4Gzj16QE2-r1YrVQqwweOk_ybmMdDF83-GVNIJjuogqRf95L_wRcTpJ3

View File

@ -0,0 +1 @@
PKIZ_eJylVtlyozgUfddXzHuqK2xOzCObMdiSzW7pzUCMwchLbNavH4GT6kmnM5OZcZWrQEhH555z75V-_GA_1TAt9IcGveHlB4CWNW8cbC9OxNrXCVKcRDuxsWuhaeqTpCmO0Wq-Mlez4FXPoGYO44lkat7F9KxYBLpjzJUtG4ynRpZFzy-dvccCKhMR5qtcfbaO7PlIzlgIdbxx97EpH63ilEXiNY_p7AaIZz1Zmi3EQsvHUZAvNSUn0eSQmPI5Prr9-2QcubdtNAmDQ8OAlXw7d7lEP9Vg2Rsd6qRmWSgV9E8S6hNhKeJ22WMOF4RCgeRYgDzsnR5FgYR93BCK6Eovc1xgAUA_3Vt5k1lHuyRCWcf5yKgjUXqOhck6pndWbHeObOwKR-0HFmCg8X9YgIHGTxYqj2l7xnzo-drI5JTO3WaVT2voW-K4gSa1qyITUY_rtDBqgAo3RxT3hNoF7oMe6ZAn_n6PCpViAUuryM5RgVskGPku5K4MlHvZqOUgrnUkNYjn4Y05MXwoY-o2sVBW6RztYrOstncr482GLZzfbXtz7RibswoLQQ7-rW2_6DUBsDh0g2D_1QnwFfJH4K_FBR_VPXQr3xrU_SwYLW84SssRkIYVmav1wAgkvHxlD69Jx5Bnt3TnNRmrB0aTf1s4qVNqfJni4JtiDcnFjcnFvP-r9eCfvB92Tmh43EZydff-TeiDXA32AxbnQKlM6GQfz76Tgc6gUQW9qYBMSwCkYGQoKpAPOdiH5co0BGiSghTZBFNLQIUh4nuiNWlkM73Qt4rpt_H-Llzwt7lOUR1vVD41PzeajdCeY3rrwWgHz8tLjbWvQQfWlUZ6QjhJRLd-z8Kv0h18w8Ke6cOjThZgLjW_pvzggvfd7vM7cPAZ_btNJWigrtQgLSw2YMsbb1jsThLzTYPILVm853R--FLAQQswCPi2uGbCjdnGaqF8matnloHjJKuwGugrN6hj9rcD6DtPSE-eYO9uwZ02243OqnSgzDoP223PwijJ-O52aRQM9v4ssPf5M7kCwyC8Z9qBbFCR0LJJzbemYk742GyGb2dy14MbwFkYu23ktNaRu9fC28eG9bmCRPs6Nllt5LY8xJ5u2NGW35klVL6yTT70S8A8ZQuC95Y2PHdWyf1COeyZrbuxqfrvFTqAwRwMKB8ayDvg8VMn7tj5WcL83bER9K7BV7uwOEdLxzBK-Ux0Vi8bXobYUjt2zCsJ1gA7_5ts6zQZkVqtUCw1Q6GqBL7iB63WK_b9HftKGfrQuTaag_XQcSyjsXXHNzwAVcVU-MBQW2gHYljFx1JgKVxC12oMZZy8MJpynZhhFYguuztcW8NX1nfgqw8041a-bBDHaoHZGTRW89fbykGd7ckr2ZR9arIWFqj1AJTcgapYtI8Auk5jZONOutHcfBK11JqhM2GAhEVkfLjeKEjNDpf9ITflhlNZ-DOgKB67B2niTXTXpH1IYeWIT09VZWNhm5pu_7LFotenk40hKN5tMWmeLuGz5F_p9Lw8CZct2Exj5Vhc1ig3oPTgy6G0cGOnnYclRPPLjp6a5elZauAxWJk7U3pep74japd2cbW6ykoJIP5aWuX7hwdztjNlszcnrfuwmnC8LJSzZ11Osktpha621jm0Jdw6epycXy3yWK5odqWiC66rXBCk-CJeBffxOaJazV2mNJhOt4l2eFXI3o0Wt2oBV3SWRiePSlr56B_UY9dRTz2YEvCb9bK-zFdQrRHO5cuZqx5fIiHT1CZ3-SQq7Cpz7MNRvjxORbSpQnmy7B7YRZI_16hsr-B6Pb2IF9vVHjxzkSbJLjhEi9h4DOIVBeNd1ED6z3vpnxbOkgI=

View File

@ -5,15 +5,15 @@ MTItMDgtMTdUMTU6MzU6MzRaIiwgImlkIjogIjAxZTAzMmM5OTZlZjQ0MDZiMTQ0
MzM1OTE1YTQxZTc5In0sICJzZXJ2aWNlQ2F0YWxvZyI6IHt9LCAidXNlciI6IHsi
dXNlcm5hbWUiOiAidXNlcl9uYW1lMSIsICJyb2xlc19saW5rcyI6IFtdLCAiaWQi
OiAiYzljODllM2JlM2VlNDUzZmJmMDBjNzk2NmY2ZDNmYmQiLCAicm9sZXMiOiBb
eyduYW1lJzogJ3JvbGUxJ30seyduYW1lJzogJ3JvbGUyJ30sXSwgIm5hbWUiOiAi
eyJuYW1lIjogInJvbGUxIn0sIHsibmFtZSI6ICJyb2xlMiJ9XSwgIm5hbWUiOiAi
dXNlcl9uYW1lMSJ9fX0xggHKMIIBxgIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkG
A1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNV
BAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEW
FmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgER
MAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIIBAFq4JvODBIaoHiYG6KMCnBEhDjWS
CuW0gq3kbi3j8kOzb4Mr7Muq0XvGMRwDrZlkfSpzIyuri/Fzf2pW58hnjWfDHQ1S
laAWLs6csh2u80hgWpMngCN5ZVFtIIbWlE0ZuLZh8p7E0IJZnNvYmlOVrmIkRo+J
1vMr71HZr5/kFcJzFVgi8QI4XU5iBPsUWOdJJV+0jXkMHVqOX3H297CYCePaotLD
azuquE74N8KMyl8j8jE9wi9O1gVBqO4L66ePjt5zI/TrjbjKwdseqoZR1dDGlp5V
awRwRYCjsKF+asAbuASOwdSgP8V6VgTOUrZh2D8KHtclwS+URoTdVl4ypQA=
MAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIIBAFvooB0VQsp6sBW+xbnGmfDKlLWS
XHA1kqS5GuI1wu+rqSGarUR4WNCuBCp4bNzfgM+mXSArL/tTCiAMhs/Lh6GOYC7w
ZOFh8GSIIcLFYThCPZa+uV/39ZaLTguMmjYPZq1EUco+NErajY6mbVGpshlb8Y5l
F6cNkrMhPbskcARi6BUWu/eObjfeb9uw13wVIyWKQIEKA1UGA0yeyi+I6kmYpyBw
U4fWKdvcvOiXiGSWZuToc/xZQV0NooQeEZI87n2AF+dGLe/7YJi7MdP7klI5u0Ue
VP1YdeRzH4a2wyNRvGjD/b8+ZNqF+WDulfWSQ+spKqz2Y4FEO6z8ZuvI4wM=
-----END CMS-----

View File

@ -0,0 +1 @@
PKIZ_eJx9VMmSozgQvfMVfa-oMAbbVRzmIAlZCFvQGLHewAs72MaY5esHuzt65tSKUEiZkS_z5RL5-TkfiAk1fiBmv4RPgVGq7kCg75qQps-jAawjamYd4QiBwUHAwgPiQIOJc1cThkg-67lDkH0jNo1lQbWwBqJZaQc4SXB2HvU0kIzyKLPMzOAXred_HV4DyVUD_5DGRKlp3iRnWWwp0kUhlh5lnNEN1dos9NM-8vXyOM4yoiPjeNxzsNpzLLsqXpo5e13Ry-gLfA0R3QizYc88p2eTnpu8kEIvEA0VSEGO55dNBi8Gw8PibCObtq7sEchO_szqd1DhWClt6BuXmJRd9It27Nt9Qqt1GnvOLP8GlEoXeMuS2e_oYywNb6YC3T6-_m_8dshxdpmdzPV4g14501p_xsQZab08_WEx44S_RHnnOL-56bGV6TlTUDlT6DmiwY0qqIKeESYLJg-kMA8LJoVZiHTl4otDkmi7ub1wSCgEHMGrimCd4x0DCQFLB8MDgwbHewYKIrwVKUOuywY0AR0mhgtBwkFhQHagPQaB6lqWhvuSn7x1d_bDuZXOgHNgvWwFCBqOHKUPvTU_kW0eTfjAwPc7EhoYtSV3fZQPz7hyBp2DHCbFLS0yovQiRBb2hG31KM--IcbSurTI29H0djSun8fqOGxVYP9ixThaGmVMgsSRyjqu3AIk-CAwcCTQbk3Q04gB8c-IzhMKgeUAONcCbO8atS73i3mAGF0iWEaZWKcHN11FAj1_r8a1F5ZGKDWGyD468ZlOstqwRb1jnp5-5fK-M-cJvXSTbE6Vxqs4Sg9dUQdNcSuE_Cfc3JzH-fqxLruP-wpoqpNGV9iP8lMuzsmGtUkY1PCeUyJHQ7Nl2vfJslSkKOoJWpOw21fD1JDztsjbyx27Hw95icVWut-JOC6a_SUK-k1AmpUrNtpjm3T5osNNEn608g1lsSOgZBVvppgUhx2vm-5ate56rZynjSgam_tr6J7awn9y4n5Lth48bJRdy6Wx8m52ju7IE1Z-G92-ldZegIXrbm6gHJuBT63Ss1g3be9i5-ZTVotYxMm5WNrPXaB2_PpzsPt_hPdKwYb633r5FzKfcIU=

View File

@ -7,6 +7,6 @@
"project": {"enabled": true, "description": null, "name": "tenant_name1", "id": "tenant_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}},
"user": {"name": "revoked_username1", "id": "revoked_user_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}},
"roles": [{"name": "role1"}, {"name": "role2"}],
"methods": ["password"],
"methods": ["password"]
}
}

View File

@ -1,6 +1,6 @@
-----BEGIN CMS-----
MIIIVgYJKoZIhvcNAQcCoIIIRzCCCEMCAQExCTAHBgUrDgMCGjCCBmMGCSqGSIb3
DQEHAaCCBlQEggZQeyJ0b2tlbiI6DQogICAgeyJjYXRhbG9nIjogW3siZW5kcG9p
MIIIVQYJKoZIhvcNAQcCoIIIRjCCCEICAQExCTAHBgUrDgMCGjCCBmIGCSqGSIb3
DQEHAaCCBlMEggZPeyJ0b2tlbiI6DQogICAgeyJjYXRhbG9nIjogW3siZW5kcG9p
bnRzIjogW3siYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0
YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwgInJlZ2lvbiI6ICJyZWdp
b25PbmUiLCAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3Yx
@ -33,15 +33,15 @@ bl9uYW1lMSJ9fSwNCiAgICAgInVzZXIiOiB7Im5hbWUiOiAicmV2b2tlZF91c2Vy
bmFtZTEiLCAiaWQiOiAicmV2b2tlZF91c2VyX2lkMSIsICJkb21haW4iOiB7Imlk
IjogImRvbWFpbl9pZDEiLCAibmFtZSI6ICJkb21haW5fbmFtZTEifX0sDQogICAg
ICJyb2xlcyI6IFt7Im5hbWUiOiAicm9sZTEifSwgeyJuYW1lIjogInJvbGUyIn1d
LA0KICAgICAibWV0aG9kcyI6IFsicGFzc3dvcmQiXSwNCiAgICB9DQp9DQoxggHK
MIIBxgIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgT
AkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazERMA8G
A1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFj
ay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIaMA0GCSqGSIb3
DQEBAQUABIIBAI9qvSGYlIHoI0gWTZ55POWwj5Gjyr/SQ4q2e13m5bSnmvawGw6Z
Xa500UvvnUPHe/Nf4ExS0/hO2zn80hjLC+uEDwESlMMDTc29+TbafHTQOp+Zz742
KdO+2Zv3UevStJK+wg+D0cKUmhmghyzzIsD44OmzLaGe+3mCqLkWFv8i7KX9rrb5
Jqv8kbLm9bY7wlWqmYcaJyXy5SdJMKi1/aWm9nn5AOaujHTfV/bygQd8ZY9t4+6k
OwKPZlt5x1KA3IxGdJUFvIHj7am6j0auQ2TMx8x2vzPzy5+mBzB0u0XaENJsb9Jz
z+aVgXOQ81PbwwZmStONrwEVpjQeyEXY4pM=
LA0KICAgICAibWV0aG9kcyI6IFsicGFzc3dvcmQiXQ0KICAgIH0NCn0NCjGCAcow
ggHGAgEBMIGkMIGeMQowCAYDVQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMC
Q0ExEjAQBgNVBAcTCVN1bm55dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYD
VQQLEwhLZXlzdG9uZTElMCMGCSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNr
Lm9yZzEUMBIGA1UEAxMLU2VsZiBTaWduZWQCAREwBwYFKw4DAhowDQYJKoZIhvcN
AQEBBQAEggEAmG4yoCylqeL51u3yAOI5hGOlOZrBT53W7GGLpTqa2NZxXJOhVhPa
UBn8idQtVOPHk66XOrGdExSy0QSvTz/Q9iFQ5Tj7NsYj3vCOIHF7UqZo1SfRJ6Ss
iE18JPYMDCt9kChcPyiNpdPpqUyHuX2Oe83yVZtfljf9u+WNv2kaNys2gwdrMKIb
rKC3/JFoEEn6ISqkRqs3+aEbe++Z0YykrFctuYvctJPARqFewmBvkvrnOXuaeIsN
kT70Zgthd/JUHp/fC2vbRtIXiskl9zVegAtu10nlHV9ff3B2YHZi1MGcwJAnwxyY
eRuWwQ5bndCczoncOpb13FbC1eeOBgrXNQ==
-----END CMS-----

View File

@ -0,0 +1 @@
PKIZ_eJylVsmSozgQvesr5l7R0Symyhz6wG5oS5jFgLixtDEY7PLC-vUjYXd31Sw1PTOOcNgIZerle7no0yfykTXDRL8p0KMPnwA0zdWywNbXU2zuuwxJTqacyNpiUhRZXCqSow2KL63kYntRC6gYFVnfLQ3FOxuemfJAdbSVlNBFSSuK6PpttJiUu9VpaT6bq2uZrawuaYIqV-7PcSjscTPU8fzsjiAPt1dTsQ4px-6TcFHapfxiNsI-Dbfkv1TGhnjDYd1G3Lw2mGVfmE19MKsT-XU7kIb6a1qLr7GqlTuPvvxpnBtBi0OBeW_s1hmHxiSSmSQUW0A9pcfgmipvPB_dOm30NtffOkb73NCvKZdRlCkJlThna3A3iLt0Fdxiz6ThEGO3T7m6zVfw--Z9bLAEaeD5NHbFOuUrt7fLZQegb_LrSmqhshjsquDRhLu80jpUuSVq8BQ3VoWn7YRUyMb-fo8qucEcXtihVaIKDwBxWrlWpDJrgiON6Y7IqmOu7tKD2D5QvaYkrIzyo79HASiM_4MCUBg_UKyCMjXqKggseJdpz-Qr6Xk9LgdYZfSAfl1pz7aa8agUOegtOYAMk4srck6DKuRDBk5BbRsaB424iqtCwI3JoUrjsWeJEVXj6AqZ8ZC5Ea8kkdj6rm_Qxiu5S4juGSteye8lG0ms-i2nMn6X7Y4sv5L8qCg_4N_K9p6vwwhs36SE_WclwN95fuf4A3LBO3Z9U4Azu38mLAnZfcxtZ4ekIg-ZIVJEE4i44TVtbhP1HLKsuFbeV2PaiBz-IMXBr5FFk8uhIbVU-7fSg4-1n08e4zB_TbnFjOg70T4nzPIDUsItqfuRlO_1lzJQoRwthvWEGVzFDYBcXGIOsnByJhRuF9jHfdygxlbrElfkjZ_v50Q7yixpZa-Y_aVi-ut4_ypc8FGuY068kRxg_txo0I7kRZvwsARUjihirrTjEh5oV6LwLnFUT7nxIwv_Nt3BP0tI-dnyax5Pdy4eKV7ONh64SyRs0uaeZbQa44hW3hBsD_09C1cuk6mnbj1pIxqpIsS5f5oIJyxAI5FlnGH2eWiRMkb_ZMhCVepnREc2B_TUfFX3j9hfYzILcqNmvn1A3J03Nqe2ZLAETGKIh3vzIKPM0KeMz7usccpZlSZYZEY9xhHa4ciZkcFKmmyF6aHHDMDWnZHAGpB66hF7evQF8RpH8N0AefSILjXIhDr-VA08oI8pN9Sw_J4LwRRH5mNOut08_h7D9o3U8zwFhPXdvOhrDxWcPwzV-kD7A333xpiEFHcJFxxAxNPT7jDho3XFyvtNjz074pzAZ8WdbyhSduqLYmUAqdBkaBoH8v0GnVOvSFgNHEfXeo2FzrVXnPnZ0Hor2E7aGkoHQ2K3miJDxWG0AWiV5MgFCmQp85UAsWkjCDkpbRKSB2XpvnkPLZ-X67RGDA7RBbpar_az4zXQ-v36R977Wg0V-OP6Qm4vluTikIQhZDwhswmklDo63h2tG3EE8aRtoWzOJ0kDXG-54BqXsp-EeRuHjiKR0-Qe61_7hSrtT73qvL1PaTKQHXo30qTi8A1d3G3mrSX5pubCKREZlaxEeZF0qnqe3Gq0mmcvvB763tW0W69v-s-RDqpRgZnLY1x4BMViY3G8gDiW3cTRsolW2uc0MOVLyz_fal5dtTiSq7TstR2f2eNmoWKwQVmIxW25t-zzywnrqrEbO_VsuJd1bWtQ1vTyKWg3ngtbQfl80c8Xd0wydeAbqJRPVxcMHty3SBcuQd0vfX_h9ofRwuYUcmWwGJJ8SL7mJRwCzcebvLt5SqHwT_LGzgaxZ3aFBBzm5Ww_7faNib7K_nR4sXH7ujkdrPPlZSva8pNYtf1zPY0o6XtJv52T6LwNfIlbdkJvSQxA-XNVOzJ7Vlipvh6Dk_2UC0vmcxS3tiN9-QLmC62G1J-X298BCSOhiw==

View File

@ -0,0 +1 @@
PKIZ_eJy9V8lyo0gQvddXzN3RYUCgNoc-FItYTCGBEEvdWCwWscjWwvL1UyDJ3W577J6YiFGEDlUFmS_zvcxKvn0jP0FWNPMvEa3HxTeANE1X2kB_bLCWnWMTWrHYkD1JEkXRSkVoyZ3oQFVINy9SikSlEEWhTxVx_aystWgGJEtWYUg2u52cprH71OtUxBzLKNfmmnrIY1U_h5VbJOJljRX-GHjlSSualDx7AoFHl-NCq-xz5C32Ucmfxj201g6aqO_x-KKo7yKGzkKP24ae1Wk1NZ6VUbXIQaS4u9FAouo0XrfEEJdF3iZf5jBPFJcY4yifmfY6LR_P7TJmzD70BSr0-BMYX45q9xCJ42E5GdNqe7R-Shb8Hktyvh0N1_qZOBvGc292yMn5Ea-1OSBQ-ojpCGSdN0Th-68I4oo_YEfLt-4E-Yh9u4kY-2Kk19vANweQMHyPRT0xRJhjP2tDXy9jms-mpIlajyTEGg7sDEdmXUnrloVMLQs48_IpRwUIfDuLFL7-HRo5ZAgyekQzGfe4Xazw-6i2X8NIfP0ALgxkVKLCudE_dKiIT0hkW6OQ50spnpk5z6D1A2MqGoMLswoqszAdRKHBLZeKzACk4AIXKRdUGmMW8iy40kc8lXGFs4C55CPw7GPosROauHLrkYHROSDZLTFTnqMdf8K3RNZuH134bxLVbpf5wxk52swo4IiO5CGdmUNwTgr5DMzCzgmwAVd6EQybwZQQjZ0sMwuhCpiAXXp6bhZBZzLy5J2IicK-XgWeWV4QVOWJCKYnyTtMQrkhyicEPfaSfcRwq6jaTHog8qXjqp3CClWbArHUnI1B7s1-TByB6DSsOcSMMQs6YwiooMAVYnAeMIhGgzWY3oYNnKDFlVktpTIHQUGOnCS7yPSCBleL4kplm3j6IfTQu-TdkIJb8vxJrjYXK9c6ICpMxkIbC0d9o486UhankZ3RKPgngXyqD0fj0KQP7QD-DecfUQ6-5nzXE48j5_8fjRwiXkca_4QZ8FmMvzMTenSGmVckU-u7ViN3Tir507L9J1bAa9mKIy3sH6nvV_GBD2LMsELvo0vHuSaRvba4S7gOw70KDHwpmi_Qgc_gfYDuKjrSeMULQvAVxK8Qgv-SwBEh-Lfl-7tGwE0kAcMfY9Wl8AcXTMi4XDwjd2f1vsWDPy1hNPZLJyZFhd4UFbhVlVYtdtF4bb8vqPVnBQXGivqgoIg3RJs9SW7_8T1xRTDOB-37hJV_fleAn41j0yIJvovxNcRaoIOq2wf9W4mDEc7mjYDo6aZO1LK9qQ-TQSNRSurplT53wL5GQhlb2m20uc5Ev3Tf17Fm2nNuLc2acnCblYDPlLvLcAF_fZmOGbd_O9rcppfRu36dlWgebB1FhHHTpqmqwFQWkKbsyP8JWU0rwkByLWshtzKNrEMrWtNakVvd3QyygeBOAZDeyKKARIuSO7mAlpCargBjR3RNOqo4LiHNlsBfIwEq07MZ0p2ZUEalSZEEvwBky63UTpYNuc0M7JdDohD6HLlEInodKMk8qUM78H7K2oURMQSi-mLJqMisNMgbJGiTJ9ghY8O4B5wLTuglJ-xZIiTOhDZYPLasBLOmlaxABz9HXFkQLEimVRnmJ3OlLmcvbKSdqMYrmzCrm95WXJ12CpbiH4Ln1O5ZzC2aZ6DndyU-zU7DXS1QL_Ndjdd-JsAIqbs9v3To5N5fB9zLshOf-uql6beRHX3H4Xy_hxWW6AqsHh-d7_NktVXtxxXTR2yhoe3cWAcs_bxqnxTBqRUha-onmROWuZpIXC05Em0v1vaB1bI50P2ZKjyrfXi33B4XFO47K4lXsKyFx7vW2Id3ZyKK9OUQMH7ztHPNY-vcQ38ZZliW5ORlDQYlpPYnVmg1NNNgWvIzt33g7oXy0LVwkMU8rNSu3g6ORWFa9GAxHL1NWqSxkdqqeL4HK0GEBs73RVma-_uGClnlMehWZR49Gdvvq8UiiqvZ1jZ0-OMHmD4xZFP6-bnxN6RCLsw=

View File

View File

@ -1 +1 @@
{"revoked":[{"id":"7acfcfdaf6a14aebe97c61c5947bc4d3","expires":"2012-08-14T17:58:48Z"}]}
{"revoked": [{"expires": "2112-08-14T17:58:48Z", "id": "dc57ea171d2f93e4ff5fa01fe5711f2a"}, {"expires": "2112-08-14T17:58:48Z", "id": "4948fb46f88c41af90b65213a48baef7"}, {"expires": "2112-08-14T17:58:48Z", "id": "dc57ea171d2f93e4ff5fa01fe5711f2a"}, {"expires": "2112-08-14T17:58:48Z", "id": "4948fb46f88c41af90b65213a48baef7"}]}

View File

@ -1,15 +1,20 @@
-----BEGIN CMS-----
MIICWgYJKoZIhvcNAQcCoIICSzCCAkcCAQExCTAHBgUrDgMCGjBpBgkqhkiG9w0B
BwGgXARaeyJyZXZva2VkIjpbeyJpZCI6IjdhY2ZjZmRhZjZhMTRhZWJlOTdjNjFj
NTk0N2JjNGQzIiwiZXhwaXJlcyI6IjIwMTItMDgtMTRUMTc6NTg6NDhaIn1dfQ0K
MYIByjCCAcYCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD
VQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sx
ETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu
c3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUrDgMCGjANBgkq
hkiG9w0BAQEFAASCAQB8CHeQzIDFNy4RcWb+RRwwZ3JZ8Vw7OnFZhWXdbQ7a5KZr
CrB/cGpkvMgnOtCNfpwSv7H97UEmOhQEarFJ6YFNNjA2FdXgt+2MCPmodG6tGyXl
F+sh/g7qE8ONTiIJn2634kCj7fAyrVUlRxDLu6Llk9KWv7pAxtJKjqirHr8i8tYM
/QV8pX/QP0u+vJvzZBXb/UlViQpaJnY9SWKF0ETXGypFTtExfiXG9qz96JWvC/jK
gOF68N0FsoCNhzRys86rrAk7jZugajzjk3y/buInZiv4npQcEMC3lHKBXtA2uiRf
ILgnF0qOttONx8GNE72eJqWBkiJf52239PxgxWNg
MIIDTwYJKoZIhvcNAQcCoIIDQDCCAzwCAQExCTAHBgUrDgMCGjCCAVwGCSqGSIb3
DQEHAaCCAU0EggFJeyJyZXZva2VkIjogW3siZXhwaXJlcyI6ICIyMTEyLTA4LTE0
VDE3OjU4OjQ4WiIsICJpZCI6ICJkYzU3ZWExNzFkMmY5M2U0ZmY1ZmEwMWZlNTcx
MWYyYSJ9LCB7ImV4cGlyZXMiOiAiMjExMi0wOC0xNFQxNzo1ODo0OFoiLCAiaWQi
OiAiNDk0OGZiNDZmODhjNDFhZjkwYjY1MjEzYTQ4YmFlZjcifSwgeyJleHBpcmVz
IjogIjIxMTItMDgtMTRUMTc6NTg6NDhaIiwgImlkIjogImRjNTdlYTE3MWQyZjkz
ZTRmZjVmYTAxZmU1NzExZjJhIn0sIHsiZXhwaXJlcyI6ICIyMTEyLTA4LTE0VDE3
OjU4OjQ4WiIsICJpZCI6ICI0OTQ4ZmI0NmY4OGM0MWFmOTBiNjUyMTNhNDhiYWVm
NyJ9XX0xggHKMIIBxgIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5T
dGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25l
QG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIa
MA0GCSqGSIb3DQEBAQUABIIBAI2tvOIhjQTThmRwK4wp+KeZOU6hKk2A5j5c+XNZ
DxwLlvuX+YZxm5OpLdInKTjMvjMi6guciwH3VUAo2Iqq9yMhH/Xh5t8QJS+zPF7i
+GOqwZpBc6QKNihIok09L4jtaUjf4/f7EZQNh/jMkftuA3hhtuLalnkYW7yEA2sF
ne7PRVUV6i8i+q7gC5L9iXk4Qy3p2AzgPGfhF6PM/0pzyjJtk6v8PBCkMBNexrkq
RS4WjsYMR3mev1jL3dJ0wTgCgnqCcR3bQtDL/MHpU6q6Ce6Q3DGE6hkQOIRHenE/
NoPZqDY/M34NHbjsMqi4zJkO4HMrcgAiYa/zX/yBwzvEurI=
-----END CMS-----

View File

@ -0,0 +1 @@
PKIZ_eJx9VEuPszgQvPMr9h6NQgIhk8N3MMaACTaBmJdvCZMxGMhjkgmPX79kRtq9rNYXq0ul6u7qVr-9Tc9EDqZ_QbJ_BW8KwdhiXe5tLxyXz4KCsICXCQstCMHYQRCiHjLgmiL-sgSBjpzwpHPg_ubs8VFTrBC54DCBsYqEsL3T4A0848_DMqmxvIhUu1c8K7tD5jXFgA0M8UAYGnwGdJ8hVUkspAUy1gMZ6mmF7xh6Vw5fRK_Ox1jjKerpaNekzVdkGau8zRe8RR1JeUNZ0SskzYd87218aK5xm-iF00wVkCqoQEUk6kmldgFUe2qHk9BlEVgXNbAvlQ9BdUjDSnkRqVWrgcOnn7eBVUpq2SWXdZfLfDGJjDkL9by1Gy6L6nPfianN5uSa16JNRuXVJ5a4Jww_iCUehEUxYYVBmTCoVR5w1QncNj9-4DaSlH00OUMaScNhSjIqnEUtl0mbM9DzNl7QEfVceiU-q3fs_r-BL_-U_zYQq8FUNm-xSttcDxyiktRuA2ZWVMaTCC2n6qo8TVqFDt4my9ReCHc77YTZC2wCBs2rBc2zRFsChAMWMTIjYlKGfALq37gkMElIr8AReKagiQkEAzU1SYQ7BHIrCUMXdQ37SFffp4yXRyfukQThL_fCYLzpeLpiyodjy8OIIgLef5RhT_B-mawKLXoe27j3GJCmqG9lXTmbTjVhiKZmHs0po-pxuWqU0PlRGn-EhtWzaIvetsD-NxNhcEGbo5OLeNmcj21SA_FKVjjm_h6ADh8UAtR_9npaaxOEMTAnLwBePp4BLmXIWNlG3VbvrrPtiQexUW7rJVjJVTHLKFesvvOb53c2y3nfroKr_4HPWybJU5LKEN9F1blaEoPLEt9um4GU7jwrV4_30NvPxp29rpSZE9w6fjULI9zSqsSXWt34unwcYvmpzz_XiIe0nEtSfz6-gVaWj2__0JzrPF0PCCzvtnI-rXdREidG9V7NbmsBV_6mymo9HLTrEoxi53yWtrEjc_U6DtJ71MbzfWfCehrqqf-qb0q011N5z0mktafnQvrah6d2TEBxvsEi0o7hw_LnxL3Gxs2AJyPULAcZZR0GOHJPZzRX6GXHb1Y-J5pO3aO8k1ulj14d6C75KgSo8sN8zOaD2Y1P9P2F_yg_dwhR69-b9Dc2l4GQ

117
examples/pki/gen_cmsz.py Normal file
View File

@ -0,0 +1,117 @@
#!/usr/bin/python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import os
from keystoneclient.common import cms
from keystoneclient import utils
CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
def make_filename(*args):
return os.path.join(CURRENT_DIR, *args)
def generate_revocation_list():
REVOKED_TOKENS = ['auth_token_revoked', 'auth_v3_token_revoked']
revoked_list = []
for token in REVOKED_TOKENS:
with open(make_filename('cms', '%s.pkiz' % name), 'r') as f:
token_data = f.read()
id = utils.hash_signed_token(token_data.encode('utf-8'))
revoked_list.append({
'id': id,
"expires": "2112-08-14T17:58:48Z"
})
with open(make_filename('cms', '%s.pem' % name), 'r') as f:
pem_data = f.read()
token_data = cms.cms_to_token(pem_data).encode('utf-8')
id = utils.hash_signed_token(token_data)
revoked_list.append({
'id': id,
"expires": "2112-08-14T17:58:48Z"
})
revoked_json = json.dumps({"revoked": revoked_list})
with open(make_filename('cms', 'revocation_list.json'), 'w') as f:
f.write(revoked_json)
encoded = cms.pkiz_sign(revoked_json,
SIGNING_CERT_FILE_NAME,
SIGNING_KEY_FILE_NAME)
with open(make_filename('cms', 'revocation_list.pkiz'), 'w') as f:
f.write(encoded)
encoded = cms.cms_sign_data(revoked_json,
SIGNING_CERT_FILE_NAME,
SIGNING_KEY_FILE_NAME)
with open(make_filename('cms', 'revocation_list.pem'), 'w') as f:
f.write(encoded)
CA_CERT_FILE_NAME = make_filename('certs', 'cacert.pem')
SIGNING_CERT_FILE_NAME = make_filename('certs', 'signing_cert.pem')
SIGNING_KEY_FILE_NAME = make_filename('private', 'signing_key.pem')
EXAMPLE_TOKENS = ['auth_token_revoked',
'auth_token_unscoped',
'auth_token_scoped',
'auth_token_scoped_expired',
'auth_v3_token_scoped',
'auth_v3_token_revoked']
# Helper script to generate the sample data for testing
# the signed tokens using the existing JSON data for the
# MII-prefixed tokens. Uses the keys and certificates
# generated in gen_pki.sh.
def generate_der_form(name):
derfile = make_filename('cms', '%s.der' % name)
with open(derfile, 'w') as f:
derform = cms.cms_sign_data(text,
SIGNING_CERT_FILE_NAME,
SIGNING_KEY_FILE_NAME, cms.PKIZ_CMS_FORM)
f.write(derform)
for name in EXAMPLE_TOKENS:
json_file = make_filename('cms', name + '.json')
pkiz_file = make_filename('cms', name + '.pkiz')
with open(json_file, 'r') as f:
string_data = f.read()
# validate the JSON
try:
token_data = json.loads(string_data)
except ValueError as v:
raise SystemExit('%s while processing token data from %s: %s' %
(v, json_file, string_data))
text = json.dumps(token_data).encode('utf-8')
# Uncomment to record the token uncompressed,
# useful for debugging
# generate_der_form(name)
encoded = cms.pkiz_sign(text,
SIGNING_CERT_FILE_NAME,
SIGNING_KEY_FILE_NAME)
# verify before writing
cms.pkiz_verify(encoded,
SIGNING_CERT_FILE_NAME,
CA_CERT_FILE_NAME)
with open(pkiz_file, 'w') as f:
f.write(encoded)
generate_revocation_list()

View File

@ -19,9 +19,12 @@ If set_subprocess() is not called, this module will pick Python's subprocess
or eventlet.green.subprocess based on if os module is patched by eventlet.
"""
import base64
import errno
import hashlib
import logging
import zlib
import six
from keystoneclient import exceptions
@ -30,6 +33,9 @@ from keystoneclient import exceptions
subprocess = None
LOG = logging.getLogger(__name__)
PKI_ASN1_PREFIX = 'MII'
PKIZ_PREFIX = 'PKIZ_'
PKIZ_CMS_FORM = 'DER'
PKI_ASN1_FORM = 'PEM'
def _ensure_subprocess():
@ -99,14 +105,30 @@ def _process_communicate_handle_oserror(process, data, files):
return output, err, retcode
def cms_verify(formatted, signing_cert_file_name, ca_file_name):
def _encoding_for_form(inform):
if inform == PKI_ASN1_FORM:
encoding = 'UTF-8'
elif inform == PKIZ_CMS_FORM:
encoding = 'hex'
else:
raise ValueError('"inform" must be either %s or %s' %
(PKI_ASN1_FORM, PKIZ_CMS_FORM))
return encoding
def cms_verify(formatted, signing_cert_file_name, ca_file_name,
inform=PKI_ASN1_FORM):
"""Verifies the signature of the contents IAW CMS syntax.
:raises: subprocess.CalledProcessError
:raises: CertificateConfigError if certificate is not configured properly.
"""
_ensure_subprocess()
data = bytearray(formatted, encoding='utf-8')
if isinstance(formatted, six.string_types):
data = bytearray(formatted, _encoding_for_form(inform))
else:
data = formatted
process = subprocess.Popen(['openssl', 'cms', '-verify',
'-certfile', signing_cert_file_name,
'-CAfile', ca_file_name,
@ -133,7 +155,10 @@ def cms_verify(formatted, signing_cert_file_name, ca_file_name):
# Error opening certificate file not_exist_file
#
if retcode == 2:
raise exceptions.CertificateConfigError(err)
if err.startswith('Error reading S/MIME message'):
raise exceptions.CMSError(err)
else:
raise exceptions.CertificateConfigError(err)
elif retcode:
# NOTE(dmllr): Python 2.6 compatibility:
# CalledProcessError did not have output keyword argument
@ -143,6 +168,45 @@ def cms_verify(formatted, signing_cert_file_name, ca_file_name):
return output
def is_pkiz(token_text):
"""Determine if a token a cmsz token
Checks if the string has the prefix that indicates it is a
Crypto Message Syntax, Z compressed token.
"""
return token_text.startswith(PKIZ_PREFIX)
def pkiz_sign(text,
signing_cert_file_name,
signing_key_file_name,
compression_level=6):
signed = cms_sign_data(text,
signing_cert_file_name,
signing_key_file_name,
PKIZ_CMS_FORM)
compressed = zlib.compress(signed, compression_level)
encoded = PKIZ_PREFIX + base64.urlsafe_b64encode(
compressed).decode('utf-8')
return encoded
def pkiz_uncompress(signed_text):
text = signed_text[len(PKIZ_PREFIX):].encode('utf-8')
unencoded = base64.urlsafe_b64decode(text)
uncompressed = zlib.decompress(unencoded)
return uncompressed
def pkiz_verify(signed_text, signing_cert_file_name, ca_file_name):
uncompressed = pkiz_uncompress(signed_text)
return cms_verify(uncompressed, signing_cert_file_name, ca_file_name,
inform=PKIZ_CMS_FORM)
# This function is deprecated and will be removed once the ASN1 token format
# is no longer required. It is only here to be used for testing.
def token_to_cms(signed_text):
copy_of_text = signed_text.replace('-', '/')
@ -225,14 +289,33 @@ def is_ans1_token(token):
return is_asn1_token(token)
def cms_sign_text(text, signing_cert_file_name, signing_key_file_name):
def cms_sign_text(data_to_sign, signing_cert_file_name, signing_key_file_name):
return cms_sign_data(data_to_sign, signing_cert_file_name,
signing_key_file_name)
def cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name,
outform=PKI_ASN1_FORM):
"""Uses OpenSSL to sign a document.
Produces a Base64 encoding of a DER formatted CMS Document
http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax
:param data_to_sign: data to sign
:param signing_cert_file_name: path to the X509 certificate containing
the public key associated with the private key used to sign the data
:param signing_key_file_name: path to the private key used to sign
the data
:param outform: Format for the signed document PKIZ_CMS_FORM or
PKI_ASN1_FORM
"""
_ensure_subprocess()
data = bytearray(text, encoding='utf-8')
if isinstance(data_to_sign, six.string_types):
data = bytearray(data_to_sign, encoding='utf-8')
else:
data = data_to_sign
process = subprocess.Popen(['openssl', 'cms', '-sign',
'-signer', signing_cert_file_name,
'-inkey', signing_key_file_name,
@ -248,12 +331,21 @@ def cms_sign_text(text, signing_cert_file_name, signing_key_file_name):
if retcode or ('Error' in err):
LOG.error('Signing error: %s' % err)
if retcode == 3:
LOG.error('Signing error: Unable to load certificate - '
'ensure you have configured PKI with '
'"keystone-manage pki_setup"')
else:
LOG.error('Signing error: %s', err)
raise subprocess.CalledProcessError(retcode, 'openssl')
return output.decode('utf-8')
if outform == PKI_ASN1_FORM:
return output.decode('utf-8')
else:
return output
def cms_sign_token(text, signing_cert_file_name, signing_key_file_name):
output = cms_sign_text(text, signing_cert_file_name, signing_key_file_name)
output = cms_sign_data(text, signing_cert_file_name, signing_key_file_name)
return cms_to_token(output)
@ -273,12 +365,12 @@ def cms_to_token(cms_text):
def cms_hash_token(token_id, mode='md5'):
"""Hash PKI tokens.
return: for asn1_token, returns the hash of the passed in token
return: for asn1 or pkiz tokens, returns the hash of the passed in token
otherwise, returns what it was passed in.
"""
if token_id is None:
return None
if is_asn1_token(token_id):
if is_asn1_token(token_id) or is_pkiz(token_id):
hasher = hashlib.new(mode)
if isinstance(token_id, six.text_type):
token_id = token_id.encode('utf-8')

View File

@ -37,6 +37,14 @@ class CertificateConfigError(Exception):
super(CertificateConfigError, self).__init__(msg)
class CMSError(Exception):
"""Error reading the certificate"""
def __init__(self, output):
self.output = output
msg = ("Unable to sign or verify data.")
super(CMSError, self).__init__(msg)
class EmptyCatalog(EndpointNotFound):
"""The service catalog is empty."""
pass

View File

@ -875,7 +875,9 @@ class AuthProtocol(object):
'Token is marked as having been revoked')
raise InvalidUserToken(
'Token authorization failed')
elif cms.is_pkiz(user_token):
verified = self.verify_pkiz_token(user_token)
data = jsonutils.loads(verified)
elif cms.is_asn1_token(user_token):
verified = self.verify_signed_token(user_token)
data = jsonutils.loads(verified)
@ -1228,7 +1230,7 @@ class AuthProtocol(object):
revoked_ids = (x['id'] for x in revoked_tokens)
return token_id in revoked_ids
def cms_verify(self, data):
def cms_verify(self, data, inform=cms.PKI_ASN1_FORM):
"""Verifies the signature of the provided data's IAW CMS syntax.
If either of the certificate files might be missing, fetch them and
@ -1236,9 +1238,9 @@ class AuthProtocol(object):
"""
def verify():
try:
return cms.cms_verify(
data, self.signing_cert_file_name,
self.signing_ca_file_name).decode('utf-8')
return cms.cms_verify(data, self.signing_cert_file_name,
self.signing_ca_file_name,
inform=inform).decode('utf-8')
except cms.subprocess.CalledProcessError as err:
self.LOG.warning('Verify error: %s', err)
raise
@ -1265,7 +1267,19 @@ class AuthProtocol(object):
raise InvalidUserToken('Token has been revoked')
formatted = cms.token_to_cms(signed_text)
return self.cms_verify(formatted)
verified = self.cms_verify(formatted)
return verified
def verify_pkiz_token(self, signed_text):
if self.is_signed_token_revoked(signed_text):
raise InvalidUserToken('Token has been revoked')
try:
uncompressed = cms.pkiz_uncompress(signed_text)
verified = self.cms_verify(uncompressed, inform=cms.PKIZ_CMS_FORM)
return verified
# TypeError If the signed_text is not zlib compressed
except TypeError:
raise InvalidUserToken(signed_text)
def verify_signing_dir(self):
if os.path.exists(self.signing_dirname):

View File

@ -53,6 +53,8 @@ class Examples(fixtures.Fixture):
# files in the signing subdirectory. In order to keep the values
# consistent between the tests and the signed documents, we read them
# in for use in the tests.
with open(os.path.join(CMSDIR, 'auth_token_scoped.json')) as f:
self.TOKEN_SCOPED_DATA = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR, 'auth_token_scoped.pem')) as f:
self.SIGNED_TOKEN_SCOPED = cms.cms_to_token(f.read())
@ -66,6 +68,19 @@ class Examples(fixtures.Fixture):
self.SIGNED_TOKEN_SCOPED_EXPIRED = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pem')) as f:
self.REVOKED_v3_TOKEN = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR, 'auth_token_scoped.pkiz')) as f:
self.SIGNED_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR, 'auth_token_unscoped.pkiz')) as f:
self.SIGNED_TOKEN_UNSCOPED_PKIZ = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pkiz')) as f:
self.SIGNED_v3_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR, 'auth_token_revoked.pkiz')) as f:
self.REVOKED_TOKEN_PKIZ = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR,
'auth_token_scoped_expired.pkiz')) as f:
self.SIGNED_TOKEN_SCOPED_EXPIRED_PKIZ = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pkiz')) as f:
self.REVOKED_v3_TOKEN_PKIZ = cms.cms_to_token(f.read())
with open(os.path.join(CMSDIR, 'revocation_list.json')) as f:
self.REVOCATION_LIST = jsonutils.loads(f.read())
with open(os.path.join(CMSDIR, 'revocation_list.pem')) as f:
@ -116,6 +131,26 @@ class Examples(fixtures.Fixture):
self.REVOKED_v3_TOKEN_LIST_JSON = jsonutils.dumps(
self.REVOKED_v3_TOKEN_LIST)
revoked_token_pkiz = self.REVOKED_TOKEN_PKIZ
if isinstance(revoked_token_pkiz, six.text_type):
revoked_token_pkiz = revoked_token_pkiz.encode('utf-8')
self.REVOKED_TOKEN_PKIZ_HASH = utils.hash_signed_token(
revoked_token_pkiz)
revoked_v3_token_pkiz = self.REVOKED_v3_TOKEN_PKIZ
if isinstance(revoked_v3_token_pkiz, six.text_type):
revoked_v3_token_pkiz = revoked_v3_token_pkiz.encode('utf-8')
self.REVOKED_v3_PKIZ_TOKEN_HASH = utils.hash_signed_token(
revoked_v3_token_pkiz)
self.REVOKED_TOKEN_PKIZ_LIST = (
{'revoked': [{'id': self.REVOKED_TOKEN_PKIZ_HASH,
'expires': timeutils.utcnow()},
{'id': self.REVOKED_v3_PKIZ_TOKEN_HASH,
'expires': timeutils.utcnow()},
]})
self.REVOKED_TOKEN_PKIZ_LIST_JSON = jsonutils.dumps(
self.REVOKED_TOKEN_PKIZ_LIST)
self.SIGNED_TOKEN_SCOPED_KEY = cms.cms_hash_token(
self.SIGNED_TOKEN_SCOPED)
self.SIGNED_TOKEN_UNSCOPED_KEY = cms.cms_hash_token(
@ -123,6 +158,13 @@ class Examples(fixtures.Fixture):
self.SIGNED_v3_TOKEN_SCOPED_KEY = cms.cms_hash_token(
self.SIGNED_v3_TOKEN_SCOPED)
self.SIGNED_TOKEN_SCOPED_PKIZ_KEY = cms.cms_hash_token(
self.SIGNED_TOKEN_SCOPED_PKIZ)
self.SIGNED_TOKEN_UNSCOPED_PKIZ_KEY = cms.cms_hash_token(
self.SIGNED_TOKEN_UNSCOPED_PKIZ)
self.SIGNED_v3_TOKEN_SCOPED_PKIZ_KEY = cms.cms_hash_token(
self.SIGNED_v3_TOKEN_SCOPED_PKIZ)
self.INVALID_SIGNED_TOKEN = (
"MIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
@ -142,6 +184,25 @@ class Examples(fixtures.Fixture):
"9999999999999999999999999999999999999999999999999999999999999999"
"0000000000000000000000000000000000000000000000000000000000000000")
self.INVALID_SIGNED_PKIZ_TOKEN = (
"PKIZ_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
"0000000000000000000000000000000000000000000000000000000000000000"
"1111111111111111111111111111111111111111111111111111111111111111"
"2222222222222222222222222222222222222222222222222222222222222222"
"3333333333333333333333333333333333333333333333333333333333333333"
"4444444444444444444444444444444444444444444444444444444444444444"
"5555555555555555555555555555555555555555555555555555555555555555"
"6666666666666666666666666666666666666666666666666666666666666666"
"7777777777777777777777777777777777777777777777777777777777777777"
"8888888888888888888888888888888888888888888888888888888888888888"
"9999999999999999999999999999999999999999999999999999999999999999"
"0000000000000000000000000000000000000000000000000000000000000000")
# JSON responses keyed by token ID
self.TOKEN_RESPONSES = {
self.UUID_TOKEN_DEFAULT: {
@ -444,6 +505,12 @@ class Examples(fixtures.Fixture):
}
},
}
self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_PKIZ_KEY] = (
self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_KEY])
self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_PKIZ_KEY] = (
self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_KEY])
self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_PKIZ_KEY] = (
self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY])
self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in
six.iteritems(self.TOKEN_RESPONSES)])

View File

@ -14,6 +14,7 @@
import calendar
import datetime
import json
import os
import shutil
import stat
@ -532,6 +533,12 @@ class CommonAuthTokenMiddlewareTest(object):
#ensure that signed requests do not generate HTTP traffic
self.assertLastPath(None)
def test_valid_signed_compressed_request(self):
self.assert_valid_request_200(
self.token_dict['signed_token_scoped_pkiz'])
# ensure that signed requests do not generate HTTP traffic
self.assertLastPath(None)
def test_revoked_token_receives_401(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
req = webob.Request.blank('/')
@ -572,10 +579,27 @@ class CommonAuthTokenMiddlewareTest(object):
self.middleware.verify_signed_token,
self.token_dict['revoked_token'])
def test_verify_signed_token_raises_exception_for_revoked_pkiz_token(self):
self.middleware.token_revocation_list = (
self.examples.REVOKED_TOKEN_PKIZ_LIST_JSON)
self.assertRaises(auth_token.InvalidUserToken,
self.middleware.verify_pkiz_token,
self.token_dict['revoked_token_pkiz'])
def assertIsValidJSON(self, text):
json.loads(text)
def test_verify_signed_token_succeeds_for_unrevoked_token(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
self.middleware.verify_signed_token(
text = self.middleware.verify_signed_token(
self.token_dict['signed_token_scoped'])
self.assertIsValidJSON(text)
def test_verify_signed_compressed_token_succeeds_for_unrevoked_token(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
text = self.middleware.verify_pkiz_token(
self.token_dict['signed_token_scoped_pkiz'])
self.assertIsValidJSON(text)
def test_verify_signing_dir_create_while_missing(self):
tmp_name = uuid.uuid4().hex
@ -671,9 +695,17 @@ class CommonAuthTokenMiddlewareTest(object):
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = self.examples.INVALID_SIGNED_TOKEN
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.response_status, 401)
self.assertEqual(self.response_headers['WWW-Authenticate'],
"Keystone uri='https://keystone.example.com:1234'")
self.assertEqual(401, self.response_status)
self.assertEqual("Keystone uri='https://keystone.example.com:1234'",
self.response_headers['WWW-Authenticate'])
def test_request_invalid_signed_pkiz_token(self):
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = self.examples.INVALID_SIGNED_PKIZ_TOKEN
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(401, self.response_status)
self.assertEqual("Keystone uri='https://keystone.example.com:1234'",
self.response_headers['WWW-Authenticate'])
def test_request_no_token(self):
req = webob.Request.blank('/')
@ -1293,9 +1325,11 @@ class v2AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
'uuid_token_bind': self.examples.UUID_TOKEN_BIND,
'uuid_token_unknown_bind': self.examples.UUID_TOKEN_UNKNOWN_BIND,
'signed_token_scoped': self.examples.SIGNED_TOKEN_SCOPED,
'signed_token_scoped_pkiz': self.examples.SIGNED_TOKEN_SCOPED_PKIZ,
'signed_token_scoped_expired':
self.examples.SIGNED_TOKEN_SCOPED_EXPIRED,
'revoked_token': self.examples.REVOKED_TOKEN,
'revoked_token_pkiz': self.examples.REVOKED_TOKEN_PKIZ,
'revoked_token_hash': self.examples.REVOKED_TOKEN_HASH
}
@ -1475,9 +1509,12 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
'uuid_token_unknown_bind':
self.examples.v3_UUID_TOKEN_UNKNOWN_BIND,
'signed_token_scoped': self.examples.SIGNED_v3_TOKEN_SCOPED,
'signed_token_scoped_pkiz':
self.examples.SIGNED_v3_TOKEN_SCOPED_PKIZ,
'signed_token_scoped_expired':
self.examples.SIGNED_TOKEN_SCOPED_EXPIRED,
'revoked_token': self.examples.REVOKED_v3_TOKEN,
'revoked_token_pkiz': self.examples.REVOKED_v3_TOKEN_PKIZ,
'revoked_token_hash': self.examples.REVOKED_v3_TOKEN_HASH
}

View File

@ -36,7 +36,7 @@ class CMSTest(utils.TestCase, testresources.ResourcedTestCase):
'no_exist_cert_file',
'no_exist_ca_file')
def test_token_to_cms_to_token(self):
def test_token_tocms_to_token(self):
with open(os.path.join(client_fixtures.CMSDIR,
'auth_token_scoped.pem')) as f:
AUTH_TOKEN_SCOPED_CMS = f.read()
@ -55,14 +55,20 @@ class CMSTest(utils.TestCase, testresources.ResourcedTestCase):
def test_cms_sign_token_no_files(self):
self.assertRaises(subprocess.CalledProcessError,
cms.cms_sign_token,
self.examples.SIGNED_TOKEN_SCOPED,
self.examples.TOKEN_SCOPED_DATA,
'/no/such/file', '/no/such/key')
def test_cms_sign_token_no_files_pkiz(self):
self.assertRaises(subprocess.CalledProcessError,
cms.pkiz_sign,
self.examples.TOKEN_SCOPED_DATA,
'/no/such/file', '/no/such/key')
def test_cms_sign_token_success(self):
self.assertTrue(
cms.cms_sign_token(self.examples.SIGNED_TOKEN_SCOPED,
self.examples.SIGNING_CERT_FILE,
self.examples.SIGNING_KEY_FILE))
cms.pkiz_sign(self.examples.TOKEN_SCOPED_DATA,
self.examples.SIGNING_CERT_FILE,
self.examples.SIGNING_KEY_FILE))
def test_cms_verify_token_no_files(self):
self.assertRaises(exceptions.CertificateConfigError,