Skip to content

Commit 717c04c

Browse files
committed
add support for writing keys with explicit curve parameters
1 parent c7e285a commit 717c04c

File tree

2 files changed

+83
-13
lines changed

2 files changed

+83
-13
lines changed

src/ecdsa/keys.py

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,9 @@ def to_string(self, encoding="raw"):
564564
assert encoding in ("raw", "uncompressed", "compressed", "hybrid")
565565
return self.pubkey.point.to_bytes(encoding)
566566

567-
def to_pem(self, point_encoding="uncompressed"):
567+
def to_pem(
568+
self, point_encoding="uncompressed", curve_parameters_encoding=None
569+
):
568570
"""
569571
Convert the public key to the :term:`PEM` format.
570572
@@ -578,16 +580,24 @@ def to_pem(self, point_encoding="uncompressed"):
578580
of public keys. "uncompressed" is most portable, "compressed" is
579581
smallest. "hybrid" is uncommon and unsupported by most
580582
implementations, it is as big as "uncompressed".
583+
:param str curve_parameters_encoding: the encoding for curve parameters
584+
to use, by default tries to use ``named_curve`` encoding,
585+
if that is not possible, falls back to ``named_curve`` encoding.
581586
582587
:return: portable encoding of the public key
583588
:rtype: bytes
584589
585590
.. warning:: The PEM is encoded to US-ASCII, it needs to be
586591
re-encoded if the system is incompatible (e.g. uses UTF-16)
587592
"""
588-
return der.topem(self.to_der(point_encoding), "PUBLIC KEY")
593+
return der.topem(
594+
self.to_der(point_encoding, curve_parameters_encoding),
595+
"PUBLIC KEY",
596+
)
589597

590-
def to_der(self, point_encoding="uncompressed"):
598+
def to_der(
599+
self, point_encoding="uncompressed", curve_parameters_encoding=None
600+
):
591601
"""
592602
Convert the public key to the :term:`DER` format.
593603
@@ -599,6 +609,9 @@ def to_der(self, point_encoding="uncompressed"):
599609
of public keys. "uncompressed" is most portable, "compressed" is
600610
smallest. "hybrid" is uncommon and unsupported by most
601611
implementations, it is as big as "uncompressed".
612+
:param str curve_parameters_encoding: the encoding for curve parameters
613+
to use, by default tries to use ``named_curve`` encoding,
614+
if that is not possible, falls back to ``named_curve`` encoding.
602615
603616
:return: DER encoding of the public key
604617
:rtype: bytes
@@ -608,7 +621,8 @@ def to_der(self, point_encoding="uncompressed"):
608621
point_str = self.to_string(point_encoding)
609622
return der.encode_sequence(
610623
der.encode_sequence(
611-
encoded_oid_ecPublicKey, self.curve.encoded_oid
624+
encoded_oid_ecPublicKey,
625+
self.curve.to_der(curve_parameters_encoding),
612626
),
613627
# 0 is the number of unused bits in the
614628
# bit string
@@ -1078,7 +1092,12 @@ def to_string(self):
10781092
s = number_to_string(secexp, self.privkey.order)
10791093
return s
10801094

1081-
def to_pem(self, point_encoding="uncompressed", format="ssleay"):
1095+
def to_pem(
1096+
self,
1097+
point_encoding="uncompressed",
1098+
format="ssleay",
1099+
curve_parameters_encoding=None,
1100+
):
10821101
"""
10831102
Convert the private key to the :term:`PEM` format.
10841103
@@ -1092,6 +1111,11 @@ def to_pem(self, point_encoding="uncompressed", format="ssleay"):
10921111
10931112
:param str point_encoding: format to use for encoding public point
10941113
:param str format: either ``ssleay`` (default) or ``pkcs8``
1114+
:param str curve_parameters_encoding: format of encoded curve
1115+
parameters, default depends on the curve, if the curve has
1116+
an associated OID, ``named_curve`` format will be used,
1117+
if no OID is associated with the curve, the fallback of
1118+
``explicit`` parameters will be used.
10951119
10961120
:return: PEM encoded private key
10971121
:rtype: bytes
@@ -1102,9 +1126,17 @@ def to_pem(self, point_encoding="uncompressed", format="ssleay"):
11021126
# TODO: "BEGIN ECPARAMETERS"
11031127
assert format in ("ssleay", "pkcs8")
11041128
header = "EC PRIVATE KEY" if format == "ssleay" else "PRIVATE KEY"
1105-
return der.topem(self.to_der(point_encoding, format), header)
1129+
return der.topem(
1130+
self.to_der(point_encoding, format, curve_parameters_encoding),
1131+
header,
1132+
)
11061133

1107-
def to_der(self, point_encoding="uncompressed", format="ssleay"):
1134+
def to_der(
1135+
self,
1136+
point_encoding="uncompressed",
1137+
format="ssleay",
1138+
curve_parameters_encoding=None,
1139+
):
11081140
"""
11091141
Convert the private key to the :term:`DER` format.
11101142
@@ -1115,6 +1147,11 @@ def to_der(self, point_encoding="uncompressed", format="ssleay"):
11151147
11161148
:param str point_encoding: format to use for encoding public point
11171149
:param str format: either ``ssleay`` (default) or ``pkcs8``
1150+
:param str curve_parameters_encoding: format of encoded curve
1151+
parameters, default depends on the curve, if the curve has
1152+
an associated OID, ``named_curve`` format will be used,
1153+
if no OID is associated with the curve, the fallback of
1154+
``explicit`` parameters will be used.
11181155
11191156
:return: DER encoded private key
11201157
:rtype: bytes
@@ -1125,14 +1162,22 @@ def to_der(self, point_encoding="uncompressed", format="ssleay"):
11251162
raise ValueError("raw encoding not allowed in DER")
11261163
assert format in ("ssleay", "pkcs8")
11271164
encoded_vk = self.get_verifying_key().to_string(point_encoding)
1128-
# the 0 in encode_bitstring specifies the number of unused bits
1129-
# in the `encoded_vk` string
1130-
ec_private_key = der.encode_sequence(
1165+
priv_key_elems = [
11311166
der.encode_integer(1),
11321167
der.encode_octet_string(self.to_string()),
1133-
der.encode_constructed(0, self.curve.encoded_oid),
1134-
der.encode_constructed(1, der.encode_bitstring(encoded_vk, 0)),
1168+
]
1169+
if format == "ssleay":
1170+
priv_key_elems.append(
1171+
der.encode_constructed(
1172+
0, self.curve.to_der(curve_parameters_encoding)
1173+
)
1174+
)
1175+
# the 0 in encode_bitstring specifies the number of unused bits
1176+
# in the `encoded_vk` string
1177+
priv_key_elems.append(
1178+
der.encode_constructed(1, der.encode_bitstring(encoded_vk, 0))
11351179
)
1180+
ec_private_key = der.encode_sequence(*priv_key_elems)
11361181

11371182
if format == "ssleay":
11381183
return ec_private_key
@@ -1142,7 +1187,8 @@ def to_der(self, point_encoding="uncompressed", format="ssleay"):
11421187
# top-level structure.
11431188
der.encode_integer(1),
11441189
der.encode_sequence(
1145-
der.encode_oid(*oid_ecPublicKey), self.curve.encoded_oid
1190+
der.encode_oid(*oid_ecPublicKey),
1191+
self.curve.to_der(curve_parameters_encoding),
11461192
),
11471193
der.encode_octet_string(ec_private_key),
11481194
)

src/ecdsa/test_pyecdsa.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,17 @@ def do_test_to_openssl(self, curve, hash_name="SHA1"):
13191319
% mdarg
13201320
)
13211321

1322+
with open("t/privkey-explicit.pem", "wb") as e:
1323+
e.write(sk.to_pem(curve_parameters_encoding="explicit"))
1324+
run_openssl(
1325+
"dgst %s -sign t/privkey-explicit.pem -out t/data.sig2 t/data.txt"
1326+
% mdarg
1327+
)
1328+
run_openssl(
1329+
"dgst %s -verify t/pubkey.pem -signature t/data.sig2 t/data.txt"
1330+
% mdarg
1331+
)
1332+
13221333
with open("t/privkey-p8.pem", "wb") as e:
13231334
e.write(sk.to_pem(format="pkcs8"))
13241335
run_openssl(
@@ -1330,6 +1341,19 @@ def do_test_to_openssl(self, curve, hash_name="SHA1"):
13301341
% mdarg
13311342
)
13321343

1344+
with open("t/privkey-p8-explicit.pem", "wb") as e:
1345+
e.write(
1346+
sk.to_pem(format="pkcs8", curve_parameters_encoding="explicit")
1347+
)
1348+
run_openssl(
1349+
"dgst %s -sign t/privkey-p8-explicit.pem -out t/data.sig3 t/data.txt"
1350+
% mdarg
1351+
)
1352+
run_openssl(
1353+
"dgst %s -verify t/pubkey.pem -signature t/data.sig3 t/data.txt"
1354+
% mdarg
1355+
)
1356+
13331357

13341358
class TooSmallCurve(unittest.TestCase):
13351359
OPENSSL_SUPPORTED_CURVES = set(

0 commit comments

Comments
 (0)