From 6e3faad9418e66b2d02e8857c3449ce3e5f93e78 Mon Sep 17 00:00:00 2001 From: Rob Austein Date: Tue, 28 Mar 2017 21:34:15 -0500 Subject: Snapshot PKCS #8 Python hacks. This will almost certainly merge into test scripts or libhal.py at some later date, right now just get it into git for archive. --- pkcs8.py | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 pkcs8.py (limited to 'pkcs8.py') diff --git a/pkcs8.py b/pkcs8.py new file mode 100644 index 0000000..cbd5c27 --- /dev/null +++ b/pkcs8.py @@ -0,0 +1,280 @@ +# Temporary sandbox for Python PKCS #8 hacks, probably integrate into +# test scripts, libhal.py, etc once have figured this out. +# +# Both PyCrpto and the Python ecdsa package have their own ASN.1, so +# why are we using yet another package? Because it's easier to +# understand, that's why. Perhaps once we've debugged this we'll +# recode it using one of the other packages to reduce external +# dependencies, but for now, pyasn1 wins on ease of debugging. +# +# Also see the "native" encode and decode routines in pyasn1, which +# supposedly encode and decode to built-in Python data types instead +# of the fancy types from the pyasn1 library. Might be simpler, but +# whole new mess so defer for now. + +# RFC 5208: PKCS #8 +# RFC 2313: PKCS #1.5 [rsa.c] +# RFC 5915: EC keys [ecdsa.c] + +from pyasn1.type.univ import Sequence, SetOf, Integer, OctetString, ObjectIdentifier, BitString, Any +from pyasn1.type.namedtype import NamedTypes, NamedType, OptionalNamedType +from pyasn1.type.namedval import NamedValues +from pyasn1.type.tag import Tag, tagClassContext, tagFormatSimple, tagFormatConstructed +from pyasn1.type.constraint import SingleValueConstraint +from pyasn1.codec.der.encoder import encode as DER_Encode +from pyasn1.codec.der.decoder import decode as DER_Decode + +class AlgorithmIdentifier(Sequence): + componentType = NamedTypes( + NamedType( "algorithm", ObjectIdentifier()), + OptionalNamedType( "parameters", Any())) + +class AttributeTypeAndValue(Sequence): + componentType = NamedTypes( + NamedType( "type", ObjectIdentifier()), + NamedType( "value", Any())) + +class Attribute(Sequence): + componentType = NamedTypes( + NamedType( "type", ObjectIdentifier()), + NamedType( "vals", SetOf(componentType = Any()))) + +# RFC 5208 + +class PrivateKeyInfo(Sequence): + componentType = NamedTypes( + NamedType( "version", Integer(namedValues = NamedValues(("v1", 0))) .subtype(subtypeSpec = Integer.subtypeSpec + SingleValueConstraint(0))), + NamedType( "privateKeyAlgorithm", AlgorithmIdentifier()), + NamedType( "privateKey", OctetString()), + OptionalNamedType( "attributes", SetOf(componentType = Attribute()) .subtype(implicitTag = Tag(tagClassContext, tagFormatConstructed, 0)))) + +class EncryptedPrivateKeyInfo(Sequence): + componentType = NamedTypes( + NamedType( "encryptionAlgorithm", AlgorithmIdentifier()), + NamedType( "encryptedData", OctetString())) + +# RFC 2313 + +class RSAPrivateKey(Sequence): + componentType = NamedTypes( + NamedType( "version", Integer() .subtype(subtypeSpec = Integer.subtypeSpec + SingleValueConstraint(0))), + NamedType( "n", Integer()), + NamedType( "e", Integer()), + NamedType( "d", Integer()), + NamedType( "p", Integer()), + NamedType( "q", Integer()), + NamedType( "dP", Integer()), + NamedType( "dQ", Integer()), + NamedType( "u", Integer())) + +# RFC 5915 + +class ECPrivateKey(Sequence): + componentType = NamedTypes( + NamedType( "version", Integer(namedValues = NamedValues(("ecPrivkeyVer1", 1))) .subtype(subtypeSpec = Integer.subtypeSpec + SingleValueConstraint(1))), + NamedType( "privateKey", OctetString()), + OptionalNamedType( "parameters", ObjectIdentifier() .subtype(explicitTag = Tag(tagClassContext, tagFormatSimple, 0))), + OptionalNamedType( "publicKey", BitString() .subtype(explicitTag = Tag(tagClassContext, tagFormatSimple, 1)))) + +# Test data, generated by OpenSSL + +der_test_keys = dict( + + ec_rfc5915 = ''' + MHcCAQEEIFWaZOsQxLwZmIK4YAuf1d8S9Pnznvzcl9TjiMpvXkCYoAoGCCqGSM49 + AwEHoUQDQgAEC/8vH5bL+3KNNF/NL+VmUKZQtjA59UsGtKP6FP4ZqFc3Y7Gie77/ + lG1/L+s/6ircB1JkI8zaE3KYd7s+7IYIEQ== + '''.decode("base64"), + + ec_pkcs8 = ''' + MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVZpk6xDEvBmYgrhg + C5/V3xL0+fOe/NyX1OOIym9eQJihRANCAAQL/y8flsv7co00X80v5WZQplC2MDn1 + Swa0o/oU/hmoVzdjsaJ7vv+UbX8v6z/qKtwHUmQjzNoTcph3uz7shggR + '''.decode("base64"), + + rsa_rfc2313 = ''' + MIIEpAIBAAKCAQEAx/N9ee3u6Z6qjw5waPhuUBYy7m6+kRfNYB8KSERGd5K2xD96 + IeyvEv+xMDA2BQ3xOummL2yAjtMZ2N7Le37nfpvtzwVWqrOHzq7OWaw/pPl1N9Lq + VSQLPoxHw3TVe69QNPVu5SeumaOGXmzTIs1pr2yVBZD/i2KYiif3BO2SgoDx7g4s + cFdg/6YiDpKYbY/yx4YN6KJxDGMM6DE0Ih8hE68flJMSIbUWIaJZo0b7XPeE9zYU + zf93VLvuYIqWYMuwTw5TSUnzeRq6ALJpf90nObduJsYEPu/i4RFlxdm5WsmOb2Tu + F7JFesEdGeT9lCxxd3CI5YTItQIBWsx0AzCS/QIDAQABAoIBAQCmC7Zvwv9cUr8g + /cSr52L0bvrstlra8wFCiYRobwp10gilAHHUKlFZXa0vb1ns6J8jZVT0nQ5FjVkx + mBMzAzgLFEJwYOaP63ckVFZYcYqI3gBR0312JvCPiL8vuZ5vkC7zS75D3qhIPlwf + ng/YHu1dGLbIYJlWjxJN6NJh7Uh1xlZcm0WAJYhJpmMIZJv2abTS4GXw4SVOyMnd + tPEgEfrK/y2PsNUPwnby6LR1cE2rxOQtb6gNCov0AAiE0BsJeE7jXa2IEl6lKoBR + ChDMAeU53pJPlcYt7ZmAgyezuEfnr4kY5Rk/nTcwTxTpzQi7Dcth8QCRqfu4wXXj + QEN7b9cBAoGBAPNjRPc/Z89jYp1IDR+R5oi7YTsLzNAIlS/t9wgrujnVdsm00xos + dd+NwvjTi7wE0fV+7u5/W9ni3077JaBGBa9+nD0iB0PgAJW+tb8HUJXABQKoTA4m + yyiAHNHgarwc1uwr+yAYqvSj7aAvIcZeXgi3qxDXEOSKuk8n57/TpPMVAoGBANJP + /9/6zxd3PdogiP0nC+piJHstexk+l4WRqGWWuRG0VTIEfBk4dQfj/UwfmTcCQxAe + D0e9EoHeVOfsv4nfOfDhGC7jHLkLpNJbc5ttgr2sZ6qIouBJ8suMDte/zZze27aU + 7epFqw0w9Y58fwRyP2u5ILYFcm+cWeplg9lY4rpJAoGBALzLs1Krn4YzDOr+Whe0 + IITN/XVFCQIStk8wo2B2MwXrvTJoDx0Ngf4AxE4qIwmdH5T0erkMmB5jK1/j12MF + DiH874tIWyRenXWLMwZU0UDoa7qM/Do6A3uOLUzsbT8wi9M1pp5WJD6S7qBED0oG + J6FRf+QXQCZYKn9+b/nQXfKlAoGADwFuPEjk5cO4Qgv4OjfC/eIqwC8qjU6N+RW/ + ciAi7ER1n6/6OsJwdzOpKvlGMUqUBl5esLuoymNWo4Wc1PV8aNdmplHGBt3x0KB9 + yyUxIt9eNiixllcwX52KoZIp1XuBKbHOl9yIq9RGcPgpB+Qu6jy3PMV+uL/rGnJL + ygIxiyECgYAwSLLuxHKGcy+oZo1vxSQlY2gOkprqK9VwjAhxGuGlJftXrL/Dkbxs + GatW0bGAyQ+VPivPl8YYhs34NCA1t1pJKczcNbHEJFN57x6AtCbHFLWB91wY96yA + 6y/Bgd45PoXryQl7+GdOAPyEYY3mq3R5vaozTraPrnD+61kpKVLJ/Q== + '''.decode("base64"), + + rsa_pkcs8 = ''' + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDH83157e7pnqqP + DnBo+G5QFjLubr6RF81gHwpIREZ3krbEP3oh7K8S/7EwMDYFDfE66aYvbICO0xnY + 3st7fud+m+3PBVaqs4fOrs5ZrD+k+XU30upVJAs+jEfDdNV7r1A09W7lJ66Zo4Ze + bNMizWmvbJUFkP+LYpiKJ/cE7ZKCgPHuDixwV2D/piIOkphtj/LHhg3oonEMYwzo + MTQiHyETrx+UkxIhtRYholmjRvtc94T3NhTN/3dUu+5gipZgy7BPDlNJSfN5GroA + sml/3Sc5t24mxgQ+7+LhEWXF2blayY5vZO4XskV6wR0Z5P2ULHF3cIjlhMi1AgFa + zHQDMJL9AgMBAAECggEBAKYLtm/C/1xSvyD9xKvnYvRu+uy2WtrzAUKJhGhvCnXS + CKUAcdQqUVldrS9vWezonyNlVPSdDkWNWTGYEzMDOAsUQnBg5o/rdyRUVlhxioje + AFHTfXYm8I+Ivy+5nm+QLvNLvkPeqEg+XB+eD9ge7V0YtshgmVaPEk3o0mHtSHXG + VlybRYAliEmmYwhkm/ZptNLgZfDhJU7Iyd208SAR+sr/LY+w1Q/CdvLotHVwTavE + 5C1vqA0Ki/QACITQGwl4TuNdrYgSXqUqgFEKEMwB5Tnekk+Vxi3tmYCDJ7O4R+ev + iRjlGT+dNzBPFOnNCLsNy2HxAJGp+7jBdeNAQ3tv1wECgYEA82NE9z9nz2NinUgN + H5HmiLthOwvM0AiVL+33CCu6OdV2ybTTGix1343C+NOLvATR9X7u7n9b2eLfTvsl + oEYFr36cPSIHQ+AAlb61vwdQlcAFAqhMDibLKIAc0eBqvBzW7Cv7IBiq9KPtoC8h + xl5eCLerENcQ5Iq6Tyfnv9Ok8xUCgYEA0k//3/rPF3c92iCI/ScL6mIkey17GT6X + hZGoZZa5EbRVMgR8GTh1B+P9TB+ZNwJDEB4PR70Sgd5U5+y/id858OEYLuMcuQuk + 0ltzm22Cvaxnqoii4Enyy4wO17/NnN7btpTt6kWrDTD1jnx/BHI/a7kgtgVyb5xZ + 6mWD2VjiukkCgYEAvMuzUqufhjMM6v5aF7QghM39dUUJAhK2TzCjYHYzBeu9MmgP + HQ2B/gDETiojCZ0flPR6uQyYHmMrX+PXYwUOIfzvi0hbJF6ddYszBlTRQOhruoz8 + OjoDe44tTOxtPzCL0zWmnlYkPpLuoEQPSgYnoVF/5BdAJlgqf35v+dBd8qUCgYAP + AW48SOTlw7hCC/g6N8L94irALyqNTo35Fb9yICLsRHWfr/o6wnB3M6kq+UYxSpQG + Xl6wu6jKY1ajhZzU9Xxo12amUcYG3fHQoH3LJTEi3142KLGWVzBfnYqhkinVe4Ep + sc6X3Iir1EZw+CkH5C7qPLc8xX64v+sackvKAjGLIQKBgDBIsu7EcoZzL6hmjW/F + JCVjaA6Smuor1XCMCHEa4aUl+1esv8ORvGwZq1bRsYDJD5U+K8+XxhiGzfg0IDW3 + WkkpzNw1scQkU3nvHoC0JscUtYH3XBj3rIDrL8GB3jk+hevJCXv4Z04A/IRhjear + dHm9qjNOto+ucP7rWSkpUsn9 + '''.decode("base64")) + + +def decode_ecpoint(ecpoint): + return { "\x02": "compressed", "\x04": "uncompressed" }[ecpoint[0]], ecpoint[1:1+len(ecpoint)/2], ecpoint[1+len(ecpoint)/2:] + +def dumpasn1(der): + from subprocess import call + from tempfile import NamedTemporaryFile + with NamedTemporaryFile() as f: + f.write(der) + f.flush() + call(("dumpasn1", "-aop", f.name)) + +if __name__ == "__main__": + + show_manual_decode = False + + ec_rfc5915 = DER_Decode(der_test_keys["ec_rfc5915"], ECPrivateKey() )[0] + ec_pkcs8 = DER_Decode(der_test_keys["ec_pkcs8"], PrivateKeyInfo())[0] + ec_pkcs8_privateKey = DER_Decode(str(ec_pkcs8["privateKey"]), ECPrivateKey() )[0] + rsa_rfc2313 = DER_Decode(der_test_keys["rsa_rfc2313"], RSAPrivateKey() )[0] + rsa_pkcs8 = DER_Decode(der_test_keys["rsa_pkcs8"], PrivateKeyInfo())[0] + rsa_pkcs8_privateKey = DER_Decode(str(rsa_pkcs8["privateKey"]), RSAPrivateKey() )[0] + + print + print "EC RFC 5915" + print ec_rfc5915.prettyPrint() + + if show_manual_decode: + print + compressed, Qx, Qy = decode_ecpoint(ec_rfc5915["publicKey"].asOctets()) + print "version: ", ec_rfc5915["version"] + print "privateKey:", str(ec_rfc5915["privateKey"]).encode("hex") + print "parameters:", ec_rfc5915["parameters"] + print "publicKey: ", compressed + print " Qx: ", Qx.encode("hex") + print " Qy: ", Qy.encode("hex") + + # This works, and lets .prettyPrint() display the ANY content properly, + # but it breaks some of the key hackery we do after all this display stuff. + #ec_pkcs8["privateKeyAlgorithm"]["parameters"] = DER_Decode(ec_pkcs8["privateKeyAlgorithm"]["parameters"])[0] + + print + print "EC PKCS #8" + print ec_pkcs8.prettyPrint() + print ec_pkcs8_privateKey.prettyPrint() + + if show_manual_decode: + print + compressed, Qx, Qy = decode_ecpoint(ec_pkcs8_privateKey["publicKey"].asOctets()) + print "version: ", ec_pkcs8["version"] + print "privateKeyAlgorithm:", ec_pkcs8["privateKeyAlgorithm"][0] + print " ", DER_Decode(ec_pkcs8["privateKeyAlgorithm"]["parameters"])[0] + print "privateKey:" + print " version: ", ec_pkcs8_privateKey["version"] + print " privateKey:", str(ec_pkcs8_privateKey["privateKey"]).encode("hex") + print " parameters:", ec_pkcs8_privateKey["parameters"] + print " publicKey: ", compressed + print " Qx: ", Qx.encode("hex") + print " Qy: ", Qy.encode("hex") + + print + print "RSA RFC 2313" + print rsa_rfc2313.prettyPrint() + + if show_manual_decode: + print + print "version:", rsa_rfc2313["version"] + print " n:", rsa_rfc2313["n"] + print " e:", rsa_rfc2313["e"] + print " d:", rsa_rfc2313["d"] + print " p:", rsa_rfc2313["p"] + print " q:", rsa_rfc2313["q"] + print " dP:", rsa_rfc2313["dP"] + print " dQ:", rsa_rfc2313["dQ"] + print " u:", rsa_rfc2313["u"] + + #rsa_pkcs8["privateKeyAlgorithm"]["parameters"] = DER_Decode(rsa_pkcs8["privateKeyAlgorithm"]["parameters"])[0] + + print + print "RSA PKCS #8" + print rsa_pkcs8.prettyPrint() + print rsa_pkcs8_privateKey.prettyPrint() + + if show_manual_decode: + print + print "version: ", rsa_pkcs8["version"] + print "privateKeyAlgorithm:", rsa_pkcs8["privateKeyAlgorithm"][0] + print "privateKey:" + print " version:", rsa_pkcs8_privateKey["version"] + print " n:", rsa_pkcs8_privateKey["n"] + print " e:", rsa_pkcs8_privateKey["e"] + print " d:", rsa_pkcs8_privateKey["d"] + print " p:", rsa_pkcs8_privateKey["p"] + print " q:", rsa_pkcs8_privateKey["q"] + print " dP:", rsa_pkcs8_privateKey["dP"] + print " dQ:", rsa_pkcs8_privateKey["dQ"] + print " u:", rsa_pkcs8_privateKey["u"] + + # Generate PKCS #8 from ECPrivateKey and check against static data + p8 = PrivateKeyInfo() + ec = ECPrivateKey() + ec["version"] = ec_rfc5915["version"] + ec["privateKey"] = ec_rfc5915["privateKey"] + ec["publicKey"] = ec_rfc5915["publicKey"] + p8["version"] = 0 + p8["privateKeyAlgorithm"] = AlgorithmIdentifier() + p8["privateKeyAlgorithm"]["algorithm"] = "1.2.840.10045.2.1" + p8["privateKeyAlgorithm"]["parameters"] = ObjectIdentifier(ec_rfc5915["parameters"]) + p8["privateKey"] = DER_Encode(ec) + der = DER_Encode(p8) + #print; dumpasn1(der) + #print; dumpasn1(der_test_keys["ec_pkcs8"]) + print; print "Reencoded PKCS #8 {} static data".format("matches" if der == der_test_keys["ec_pkcs8"] else "doesn't match") + + # Generate ECPrivateKey from PKCS #8 and check against static data + ec = ECPrivateKey() + ec["version"] = ec_pkcs8_privateKey["version"] + ec["privateKey"] = ec_pkcs8_privateKey["privateKey"] + ec["parameters"] = str(DER_Decode(ec_pkcs8["privateKeyAlgorithm"]["parameters"])[0]) + ec["publicKey"] = ec_pkcs8_privateKey["publicKey"] + der = DER_Encode(ec) + #print; dumpasn1(der) + #print; dumpasn1(der_test_keys["ec_rfc5915"]) + print; print "Reencoded PKCS #8 {} static data".format("matches" if der == der_test_keys["ec_rfc5915"] else "doesn't match") + + # Paranoia: Make sure we really can load the RFC 5915 we just generated. + from ecdsa.keys import SigningKey + sk = SigningKey.from_der(der) + print; print "ECDSA Python library parse of reencoded PKCS #8 data: {!r}".format(sk) -- cgit v1.2.3