aboutsummaryrefslogtreecommitdiff
path: root/pkcs8.py
blob: 295fbb496980838587ffd9a0b39ed72f21437050 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# 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"))