# 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 from ecdsa import der as ECDSA_DER from ecdsa.util import oid_ecPublicKey, encoded_oid_ecPublicKey from ecdsa.keys import SigningKey from ecdsa.curves import find_curve 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")) # Try doing same thing with ecdsa package ASN.1 utilities. sk = SigningKey.from_der(der_test_keys["ec_rfc5915"]) vk = ECDSA_DER.encode_bitstring("\x00\x04" + sk.get_verifying_key().to_string()) ec = ECDSA_DER.encode_sequence(ECDSA_DER.encode_integer(1), ECDSA_DER.encode_octet_string(sk.to_string()), ECDSA_DER.encode_constructed(1, vk)) p8 = ECDSA_DER.encode_sequence(ECDSA_DER.encode_integer(0), ECDSA_DER.encode_sequence(encoded_oid_ecPublicKey, sk.curve.encoded_oid), ECDSA_DER.encode_octet_string(ec)) print(); print("ECDSA-library PKCS #8 encoding {} pyasn1 PKCS #8 encoding".format("matches" if p8 == 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. sk = SigningKey.from_der(der) print(); print("ECDSA Python library parse of reencoded PKCS #8 data: {!r}".format(sk)) # Same thing with ecdsa package ASN.1 utilities. car, cdr = ECDSA_DER.remove_sequence(der_test_keys["ec_pkcs8"]) assert cdr == "" version, cdr = ECDSA_DER.remove_integer(car) assert version == 0 car, ec = ECDSA_DER.remove_sequence(cdr) oid, cdr = ECDSA_DER.remove_object(car) assert oid == oid_ecPublicKey oid, cdr = ECDSA_DER.remove_object(cdr) curve = find_curve(oid) assert cdr == "" car, cdr = ECDSA_DER.remove_octet_string(ec) assert cdr == "" car, cdr = ECDSA_DER.remove_sequence(car) assert cdr == "" version, cdr = ECDSA_DER.remove_integer(car) assert version == 1 privkey, cdr = ECDSA_DER.remove_octet_string(cdr) tag, car, cdr = ECDSA_DER.remove_constructed(cdr) assert tag == 1 assert cdr == "" pubkey, cdr = ECDSA_DER.remove_bitstring(car) assert cdr == "" assert pubkey[:2] == "\x00\x04" sk = SigningKey.from_string(privkey, curve) print(); print("ECDSA-library PKCS #8 decoding {} pyasn1 PKCS #8 decoding".format( "matches" if der == sk.to_der() else "doesn't match"))