Dear all,
all versions of OpenSSL do not encode private EC key correctly.
This shows up every time the private key is at least one byte shorter than
the order. If the private key has full length then the encoding looks
correct.

Consider the following three ECPrivateKey encondings:

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/oAoGCCqGSM49
AwEHoUQDQgAE9Es5dZoubbcjpvkCSZct/QjpU4Dx/KRw6s0dA+Xt8hS++vzPIjyg
ZfCg207qk/8GohFvyoH3pKlDao2RegLe3g==
-----END EC PRIVATE KEY-----

-----BEGIN EC PRIVATE KEY-----
MGYCAQEEDwAAAAAAAAAAAAAAAAAA/6AKBggqhkjOPQMBB6FEA0IABPRLOXWaLm23
I6b5AkmXLf0I6VOA8fykcOrNHQPl7fIUvvr8zyI8oGXwoNtO6pP/BqIRb8qB96Sp
Q2qNkXoC3t4=
-----END EC PRIVATE KEY-----

-----BEGIN EC PRIVATE KEY-----
MFgCAQEEAf+gCgYIKoZIzj0DAQehRANCAAT0Szl1mi5ttyOm+QJJly39COlTgPH8
pHDqzR0D5e3yFL76/M8iPKBl8KDbTuqT/waiEW/KgfekqUNqjZF6At7e
-----END EC PRIVATE KEY-----

Despite the different base64 encodings all the corresponding private keys
are the same, and therefore any signature made by one can be verified by
any other. You may try it out:
        openssl dgst -sign key1.pem -out ec.sig test.txt
        openssl dgst prverify key2.pem -signature ec.sig test.txt

Try also the OpenSSL "ec" command with the -text option on these keys. You
can see that the private key is always the same (0xFF) and that OpenSSL
recodes the keys. The PEM output is always identical to the last of the
three.

This is not correct. The SEC1 specification OpenSSL is using requires the
full encoding in byte length of the order as e.g. given by the first of
these three encodings.

The patch that works is given as annex. It is applicable to 1.0.1j as well
as to 1.0.2-beta3. There is no strong need to change any other things,
even the documentation ec.pod is already correct (beside the description
of the very strange -modulus option in line 96++.

Attached is also a deeper analysis made by an answer to Douglas E Engert,
who replies to my e-mail on openssl-dev with a different but
non-appropriate subject line (EC key generation is broken in all
versions). Sorry for this misleading subject line, not the key generation
is broken, but the encoding.

Regards,
/Ann.

Attachment: 1.0.2-beta3.ec_pk_enc.patch
Description: Binary data

Hi Douglas,
thank you for pointing me to PCKS#15 encoding of PrivateECKeyAttributes and 
thanks for spending time on this issue.

Sorry for the long answer, there were many points to be considered.

Keep in mind: The proposed patch does not change any bits on the wire.

Kind regards,
/Ann.


1. PrivateECKeyAttributes vs. ECPrivateKey
OpenSSL generates an EC key with the command
        openssl ecparam -genkey -noout -name prime256v1 
not in the PKCS#15 format but according to a SEC1 structure (cf. 
crypto/ec/ec_asn1.c line 191), which is defined in 
http://www.secg.org/sec1-v2.pdf (version 2.0 Annex C.4 p.108):

 ECPrivateKey ::= SEQUENCE {
        version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
        privateKey OCTET STRING,
        parameters [0] ECDomainParameters {{ SECGCurveNames }} OPTIONAL,
        publicKey [1] BIT STRING OPTIONAL
 }

The ASN.1 definition which is used by OpenSSL can be found in ec_asn1.c too 
(cf. lines 266-271):

 ASN1_SEQUENCE(EC_PRIVATEKEY) = {
        ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG),
        ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING),
        ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0),
        ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1)
 } ASN1_SEQUENCE_END(EC_PRIVATEKEY)

So OpenSSL uses the structure of SEC1 and refers to SEC1 (cf. also 
doc/apps/ec.pod lines 31++). 
Why not to follow consequently the requirement of SEC1 (see v2.0 p. 109)?

-----BEGIN LaTeX-----
\item The component \texttt{privateKey} is the private key defined to be the 
octet string of length $\lceil \log_2 n/8\rceil$ (where $n$ is the order of the 
curve) obtained from the unsigned integer via the encoding of Section 2.3.7.
-----END LaTeX-----

This is almost the same text as given in RFC 5915. The encoding to be used is 
given in the Section 2.3.7 of SEC1. The specifcations in PKCS#15 and RFC 3447 
are not applicable here.


2. SEC1 vs. RFC 5480
Note the subtle difference in the definitions of SEC1 and RFC 5915. SEC1 uses 
ECDomainParameters{{ SECGCurveNames }}, restricted to SECGCurveNames, whereas 
RFC 5480 and RFC 5915 use (unrestricted) ECParameters, defined in RFC 5480. 
Note also that the corrected definition of RFC5915 is

 ECPrivateKey ::= SEQUENCE {
        version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
        privateKey     OCTET STRING,
        parameters [0] ECParameters OPTIONAL,
        publicKey  [1] BIT STRING OPTIONAL
 }

because the type ECParameters defined in RFC 5480 is not parametrized (see 
http://www.rfc-editor.org/errata_search.php?rfc=5915).
OpenSSL uses the definition of RFC 3279 (ECpkParameters) obsoleted by RFC 5480, 
which changes only the naming but not the bits on the wire ("ECpkParameters" of 
RFC 3279 becomes "ECParameters" in RFC 5480, and "ECParameters" of RFC 3279 
becomes "SpecifiedECDomain" in RFC 5480 according to SEC1v2).


3. Issue #3465 of RT
http://rt.openssl.org/Ticket/Display.html?id=3465&user=guest&pass=guest
This is *not* releated to the wrong private key encoding. It addresses the fact 
that the OpenSSL command "ec" fails to parse a generic (without parameters 
component) ECPrivateKey. But this is not failure at all, because the OpenSSL 
command ec is certainly restricted to the structures generated and used by 
OpenSSL itself. OpenSSL does not claim that it parses or accepts an arbitrary 
ECPrivateKey structure.

The Doctor said "A private key without parameters is unusable anyway". 
Additionally the RFC 5915 mandates the use of the parameters component (RFC 
5915 p. 3):
        Though the ASN.1 indicates that the parameters field is
        OPTIONAL, implementations that conform to this document MUST
        always include the parameters field.
Therefore OpenSSL is fully conformant with the RFC 5915 (in this case 

Truth be told, OpenSSL fails to parse an ECPrivateKey structure even if the 
parameters field is present but is implicitCurve (aka implicitCA aka NULL). 
This is in line with RFC 5480 which deprecates the use of NULL parameter in 
PKIX:
        implicitCurve allows the elliptic curve domain parameters 
        to be inherited.  This choice MUST NOT be used.
But for OpenSSL a private key with NULL parameters is unusable too.

Note that OpenSSL can handle keys with the specifiedCurve parameters and also 
ECPrivateKey with the optional publicKey field missing.


4. Recoding of private keys
Consider the following "three" ECPrivateKeys:

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/oAoGCCqGSM49
AwEHoUQDQgAE9Es5dZoubbcjpvkCSZct/QjpU4Dx/KRw6s0dA+Xt8hS++vzPIjyg
ZfCg207qk/8GohFvyoH3pKlDao2RegLe3g==
-----END EC PRIVATE KEY-----
-----BEGIN EC PRIVATE KEY-----
MGYCAQEEDwAAAAAAAAAAAAAAAAAA/6AKBggqhkjOPQMBB6FEA0IABPRLOXWaLm23
I6b5AkmXLf0I6VOA8fykcOrNHQPl7fIUvvr8zyI8oGXwoNtO6pP/BqIRb8qB96Sp
Q2qNkXoC3t4=
-----END EC PRIVATE KEY-----
-----BEGIN EC PRIVATE KEY-----
MFgCAQEEAf+gCgYIKoZIzj0DAQehRANCAAT0Szl1mi5ttyOm+QJJly39COlTgPH8
pHDqzR0D5e3yFL76/M8iPKBl8KDbTuqT/waiEW/KgfekqUNqjZF6At7e
-----END EC PRIVATE KEY-----

Despite the different base64 encodings all the corresponding private keys are 
the same, so any signature made by one can be verified by any other:
        openssl dgst -sign key1.pem -out ec.sig test.txt
        openssl dgst prverify key2.pem -signature ec.sig test.txt

Try out the OpenSSL "ec" command with the -text option. You can see that 
OpenSSL recodes the keys and the PEM output is always the last of them.
But this is the wrong one. Only the first encoding conforms to the 
specification.

This recoding must be corrected to solve the contradiction of description and 
realization. To conform to its own specification OpenSSL shall recode an 
ECPrivateKey into the first of the given three variants (fixed length OCTET 
STRING). The patch that works is given as annex. It is applicable to 1.0.1j as 
well as to 1.0.2-beta3. There is no strong need to change any other things even 
the documentation ec.pod is already correct (beside the description of the very 
strange -modulus option in line 96++ .

Please check the patches carefully.


5. full SEC1/RFC5480 conformance
To acchieve a complete SEC1/RFC5480 conformance some identifiers should be 
renamed. Apply the following sed commands (in this order) to all files in apps 
and crypto directory:
s/ec_parameters_st/ec_specifiedDomain_st/g
s/ecpk_parameters_st/ec_parameters_st/g
s/implicitlyCA/implicitCurve/g
s/ECPARAMETERS/SPECIFIEDCURVE/g
s/ECParameters/SpecifiedCurve/g
s/ECPKPARAMETERS/ECPARAMETERS/g
s/ECPKParameters/ECParameters/g
s/ecpkparameters/ecparameters/g
s/parameters2group/specifiedCurve2group/g
s/group2parameters/group2specifiedCurve/g
s/pkparameters2group/ecparameters2group/g
s/group2pkparameters/group2ecparameters/g

There are two header files to be considered (ec.h and pem.h) carefully. Here 
the old names may be kept as aliases because there are no name collisions, if 
I'm not wrong. I will provide the full patch on request.

Note also that this renaming does not change any bits on the wire, too.  
_______________________________________________
openssl-dev mailing list
openssl-dev@openssl.org
https://mta.opensslfoundation.net/mailman/listinfo/openssl-dev

Reply via email to