SSL certificate signature verification - ssl

I'm trying to verify certificate on an embedded board manually because it doesn't support Openssl or other libraries. However it does have libraries for MD5 and SHA1 hashes and RSA encryption.
From what I understand to verify the certificate, first compute the SHA1 hash (or MD5) of the certificate; then decode the signature using CA's public key to obtain a hashed value. These two hash values should be the same.
SHA1 hash produces a 20 byte value and MD5 produces 16 byte value. However, RSA decoding of the signature does not. If the CA's key is 1024 bits, the decode signature will be 80bytes; if the CA's key is 512 bits, the decoded signature will be 40 bytes. So I can't really compare the 20 bytes SHA1 value against the 40 or 80 bytes Decoded Signature.
Am I doing something wrong? Or am I missing a step?

The missing bit is the padding algorithm.
You use RSA with a specific padding algorithm, and the same algorithm also tells you how to take the result (when you decrypt the signature) and extract from it the actual data (the hash).

Related

Generate weak X509 certificate

I need to generate weak certificate for CTF challenge using RSA and small modulus so it's factorable. It should be about 64 bits.
I've tried to generate that using OpenSSL as I would the normal one, but it forbids creating certificate with modulus lower than 512 bits for security reasons. So I've changed the source files of OpenSSL so it doesn't checks the bit length and recompiled that again. I was then able to create private key using smaller modulus, but trying to create certificate using that key (or new small one) evoked new error which I don't fully understand. I even wanted to evade the OpenSSL problem at all using Python, but that just showed that it uses OpenSSL too and had exactly same problems.
Generating small private key:
$ openssl genrsa -out acn.pem 64
-----BEGIN PRIVATE KEY-----
MFQCAQAwDQYJKoZIhvcNAQEBBQAEQDA+AgEAAgkAlz0xJ3uUx5UCAwEAAQIIR1Zs
1Wo4EQECBQDHPJNVAgUAwlPjQQIFAKqNunkCBClt4QECBHlHx1Q=
-----END PRIVATE KEY-----
Generating certificate:
$ openssl req -key acn.pem -new -out domain.csr
...
140561480598848:error:04075070:rsa routines:RSA_sign:digest too big for rsa key:../crypto/rsa/rsa_sign.c:100:
140561480598848:error:0D0DC006:asn1 encoding routines:ASN1_item_sign_ctx:EVP lib:../crypto/asn1/a_sign.c:224:
I found that this thread could be helpful as I could even choose my own numbers, but the method didn't worked for me:
How to Generate rsa keys using specific input numbers in openssl?
Here's sample certificate from PicoCTF. I would like to create similar one.
-----BEGIN CERTIFICATE-----
MIIB6zCB1AICMDkwDQYJKoZIhvcNAQECBQAwEjEQMA4GA1UEAxMHUGljb0NURjAe
Fw0xOTA3MDgwNzIxMThaFw0xOTA2MjYxNzM0MzhaMGcxEDAOBgNVBAsTB1BpY29D
VEYxEDAOBgNVBAoTB1BpY29DVEYxEDAOBgNVBAcTB1BpY29DVEYxEDAOBgNVBAgT
B1BpY29DVEYxCzAJBgNVBAYTAlVTMRAwDgYDVQQDEwdQaWNvQ1RGMCIwDQYJKoZI
hvcNAQEBBQADEQAwDgIHEaTUUhKxfwIDAQABMA0GCSqGSIb3DQEBAgUAA4IBAQAH
al1hMsGeBb3rd/Oq+7uDguueopOvDC864hrpdGubgtjv/hrIsph7FtxM2B4rkkyA
eIV708y31HIplCLruxFdspqvfGvLsCynkYfsY70i6I/dOA6l4Qq/NdmkPDx7edqO
T/zK4jhnRafebqJucXFH8Ak+G6ASNRWhKfFZJTWj5CoyTMIutLU9lDiTXng3rDU1
BhXg04ei1jvAf0UrtpeOA6jUyeCLaKDFRbrOm35xI79r28yO8ng1UAzTRclvkORt
b8LMxw7e+vdIntBGqf7T25PLn/MycGPPvNXyIsTzvvY/MXXJHnAqpI5DlqwzbRHz
q16/S1WLvzg4PsElmv1f
-----END CERTIFICATE-----
I need to generate weak certificate for CTF challenge using RSA and small modulus so it's factorable. It should be about 64 bits.
It's impossible to do that as a self-signed certificate, because proper RSA signing can't work with keys that small.
RSA-SSA-PKCS1_v1.5 is the shortest structured RSA signature padding, and it's structured as (per https://datatracker.ietf.org/doc/html/rfc8017#section-9.2):
DigestInfo ::= SEQUENCE {
digestAlgorithm AlgorithmIdentifier,
digest OCTET STRING
}
The shortest possible encoding for that structure is 9 bytes... and that's with a 0-byte hash algorithm:
30 07 // SEQUENCE (7 content bytes)
30 03 // digestAlgorithm: SEQUENCE (3 bytes)
06 01 00 // OBJECT IDENTIFIER 0.0 ({itu-t(0) recommendation(0)})
// omit implicit NULL
04 00 // digest (empty)
So, with our empty hash we need to keep going:
If emLen < tLen + 11, output "intended encoded message length too short" and stop.
emLen is the length of the modulus (in bytes) and tLen is the length of our encoded structure (9 bytes+).
That makes 20 bytes (160 bits) the shortest possible RSA key to do anything that might stand a chance of being regarded as an RSA signature... which produced a pointless signature (since everything collides under a 0-bit hash).
If you are comfortable stomping on a 1-byte OID for your CTF, your RSA key modulus would need to be 20 bytes + the length of the intended hash (in bytes). Since there's no 1-byte OID that identifies an existing hash algorithm (and no defined hash algorithm for use with certificates that's that small), no existing easy-to-use tool can do this for you.
You could invent a new form of RSA signature padding, of course, using something like a 60-bit hash processed directly with your 64-bit key. That'll require even further work on your part.
You're basically reduced to using a DER writer (or writing your own) to hand-craft a certificate.
Here's sample certificate from PicoCTF. I would like to create similar one.
That certificate is not self-signed. It has a 2048-bit certificate signature, from RSA-SSA-PKCS1_v1.5 with MD2. So while it describes a short RSA key (53 bit modulus) it was signed with something "proper". If that's what you're after, the general flow would be something like
Create a normal self-signed cert
Create your small RSA key
Build a new cert, signed by the first cert, containing the small RSA key.
It's hard to encode a CSR for the small key (because it can't self-sign the request), but maybe there's a way to get openssl req -new to do something other than self-sign, which would allow skipping the intermediate CSR. (Or use library tools like .NET's CertificateRequest, or OpenSSL's APIs instead of their CLI tool, or whatever.)

computing the exchange hash for ecdsa-sha2-nistp256

I am writing code for an SSH server and can not get past the Elliptic Curve Diffie-Hellman Key Exchange Reply part of the connection. The client also closes the connection and says "Host Key does not match the signature supplied".
I am using putty as the client and a PIC micro-controller is running the server code.
From RFC 5656 [SSH ECC Algorithm Integration] :
"The hash H is formed by applying the algorithm HASH on a
concatenation of the following:
string V_C, client's identification string (CR and LF excluded)
string V_S, server's identification string (CR and LF excluded)
string I_C, payload of the client's SSH_MSG_KEXINIT
string I_S, payload of the server's SSH_MSG_KEXINIT
string K_S, server's public host key
string Q_C, client's ephemeral public key octet string
string Q_S, server's ephemeral public key octet string
mpint K, shared secret
"
the host key algorithm and key exchange algorithm is ecdsa-sha2-nistp256 and ecdh-sha2-nistp256 respectively.
referring to RFC 4251 for data type representations, as well as the source code in openSHH (openBSD) this is what I have concatenated.
4 bytes for then length of V_C followed by V_C
4 bytes for then length of V_S followed by V_S
4 bytes for length of I_C followed by I_C (payload is from Message Code to the start of Random Padding)
4 bytes for length of I_S followed by I_S (payload is from Message Code to the start of Random Padding)
4 bytes for the length of K_S followed by K_S (for K_S I used the same group of bytes that is used to calculate the fingerprint)
4 bytes for the length of Q_C followed by Q_C (i used the uncompressed string which has length of 65 - 04||X-coordinate||Y-coordinate)
4 bytes for the length of Q_S followed by Q_S
4 bytes for the length of K followed by K (length is 32 or 33 depending is the leading bit is set or not. If it is set then K is preceded by a 00 byte)
Once concatenated I hash it with SHA256 because I'm using NISTP256. SHA256 outputs 32 bytes which is the size of the curve, so I take the whole SHA256 output and perform the signature algorithm on it.
I can never get the correct signature from my message concatenation.
I know my signature algorithm is correct because given the message hash output I can get the correct signature.
I know my shared secret is correct because I get the same output as online shared secret calculators.
I know the SHA256 is correct because I get the same result using online calculators.
This leads me to assume the error is in the concatenation of the exchange hash.
Any help is greatly appreciated, thanks.
ECDSA signature generation is non-deterministic, i.e. part of the input is the hash and part of the input consists of random bytes. So whatever you do, you will always get a different signature. This is all right because signature verification will still work.
The only way to get a repeated signature is to mess with the random number generator (during testing, you don't want to sign two values using the same random number: you'd expose the private key!).

Given a PEM document, is it possible to know the format of the bytes from it, or does need information a priori?

Given an arbitrary (valid!) private or public key encoded inside of a PEM, with the pre-encapsulation boundary and post-encapsulation boundaries intact, is it possible to know exactly what format the bytes take (i.e. are they OpenSSL traditional, PKCS8, X.509 SubjectPublicKeyInfo, etc.), or does one need some a priori information to properly decode them?
With certificates the situation is almost straightforward - there the boundary line specifies what is expected (a certificate or a private key).
In OpenPGP armored data the boundary line also tells you what is expected - the key(s) or the data.
SSH keys created by several SSH applications have the same boundary lines but different format of the key itself. So you need to try reading the data in all expected formats.
RSA public keys usually have RSA 1.5 format so you can assume that you have an RSA key.
PKCS#12 is not usually wrapped to PEM (I never saw such files). The same goes for PKCS8.
PKCS#7 certificate storages are sometimes PEM-encoded and they have something like BEGIN CERTIFICATE STORAGE in their boundary line.
To sum it up - to some extent you can rely on the boundary line text, but this doens't give you a 100% guarantee.
You should have a look at the specification, PKCS#8 is specified in RFC5958 in Section 5
When .p8 files are PEM encoded they use the .pem file extension.
PEM encoding is either the Base64 encoding of the DER-encoded
EncryptedPrivateKeyInfo sandwiched between:
-----BEGIN ENCRYPTED PRIVATE KEY-----
-----END ENCRYPTED PRIVATE KEY-----
or the Base64 encoding, see Section 4 of [RFC4648], of the DER-encoded PrivateKeyInfo sandwiched between:
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY----
Slightly longer answer: "Between the tags is in any case valid Base64 Encoded DER encoded ASN.1"

what exactly should be certificate hash materials? verify x.509 certificate

So the hash materials are TBS certificate field. is This field values should be asn.1 decoded values or asn.1 encoded values? and id is '1.2.840.113549.1.1.5' or 'sha1RSA' ??
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] EXPLICIT Extensions OPTIONAL
-- If present, version MUST be v3
}
As stated by RFC5280 section 4.1
For signature, the data that is to be signed is encoded using the ASN.1 encoding rules (DER) [X.690]. ASN.1 DER encoding is a tag, length, value encoding system for each element.
So it should be asn.1 encoded values.
As for the signing algorithms, RFC5280 specifies supported algorithms in section 4.1.1.2. There are references to 3 other RFCs.
i.e. RFC5280 contains an example of self-signed certificate where signing algorithm used was sha1-with-rsa-signature (1.2.840.113549.1.1.5).
HERE IS MY OWN CERTIFICATE
`
-----BEGIN CERTIFICATE-----
MIIDRjCCAi4CCQDkkGcrVq7txTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJN
TjELMAkGA1UECAwCVUIxCzAJBgNVBAcMAlVCMQ0wCwYDVQQKDARDU01TMQ0wCwYD
VQQLDARDU01TMQswCQYDVQQDDAJBTTERMA8GCSqGSIb3DQEJARYCQW0wHhcNMTIw
NTAyMDg1OTM4WhcNMTIwNjAxMDg1OTM4WjBlMQswCQYDVQQGEwJNTjELMAkGA1UE
CAwCVUIxCzAJBgNVBAcMAlVCMQ0wCwYDVQQKDARDU01TMQ0wCwYDVQQLDARDU01T
MQswCQYDVQQDDAJBTTERMA8GCSqGSIb3DQEJARYCQW0wggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCa3xtoW4jog2RFjxSfCd95cq7Oxy6INdCIA4ryxory
ebDj5OcHvmAY2sdWeg8+2FPklSMDbpp+rPgUhWKhcQENIUR0JzmELJK8wIZ3Bb0+
PIkjr3ikRkgQ53sPza1MPS6+p9QbntaITXFjxLoSlo3nSIyx9jUBcUixSf1sedZc
/Qa1TlAuufqb4g9tfe6iekz4AocgisITktqak+rxsYBdv36IEw1wc+oaZezwbKkN
jdpEikiiLG5jO+IYHYXbsameL+baHKxhq/4QL4NLEjr4rHxn+61TarQrgsQvVi9f
LMDhjwITERi60Kd/7bz0jhXMum5lOXSQHcxo3hVTZbHjAgMBAAEwDQYJKoZIhvcN
AQEFBQADggEBAHN5mC9RVm9KvIGV14E5LZjYDYiVHKTs4A8Nm+OD7mWMy7Ok8I1u
gfex3tVYf9izYrhVy++0SU2MHoLCOqRcfXT+kjLEqnNVmU+HYKixhizSYs/Kui0/
alwHrAvrHY88ngwCFrVVk+aOVg2w0zkjh8vvFRs9SgMavsxpXi4RYkhcDz4SnVZk
rMJeqvW4EyW5nPOd6NGTb21uZLdMTl/tfR8CA67W1dSbMqH28eitmUw/1wjhxLip
ioak/4RSmxfrVU0UWNzppCRte2yaNo/6uQdGYK7vY/9QuzrxyhtCYtunLtYmixOD
zVgAVbqLKLBTN7bT84PYEoORWfG0UnBxJU0=
-----END CERTIFICATE-----
`
AND this is my decrypted signature :
0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a05000414752d3360bc92e11322e1fa540b4a88e8c8b1f6f6
pure hash: 752d3360bc92e11322e1fa540b4a88e8c8b1f6f6 ==TBS certificate hash should be like this
and I calculate BASE64 decode get DER certificate. THEN FILTER TBS certificate and (byte code) sha1 hash. Then convert hex=752d3360bc92e11322e1fa540b4a88e8c8b1f6f6. THANKS #PEPO

Signing 20-byte message with 256-bit RSA key working with openssl.exe but not in code

I have a 256-bit private key that I want to use to sign a SHA-1 digest (20 bytes). Using openssl directly it seems to work
echo doesntmatter | openssl dgst -sha1 -binary | openssl rsautl -sign -inkey 256bit_private_key.pem | openssl enc -base64
gives me a Base64 output as expected.
But doing it with the OpenSSL fails with "error:04075070:rsa routines:RSA_sign:digest too big for rsa key". As you can see below, I'm passing the 20-byte (SHA_DIGEST_LENGTH=20) SHA-1 digest as input to RSA_sign. Even with padding it shouldn't be more than the maximum of 32 bytes that I can encrypt with a 256 bit modulus key?!
unsigned char digest[SHA_DIGEST_LENGTH];
SHA1(message, messageSize, digest);
unsigned int privateKeySize = RSA_size(privateKey); // 256 bits = 32 bytes
unsigned char* signature = new unsigned char[privateKeySize];
unsigned int signatureSize;
int res = RSA_sign(NID_sha1, digest, SHA_DIGEST_LENGTH, signature, &signatureSize, privateKey);
if(res == 0)
{
int err = ERR_get_error(); // 67588208
char *s = ERR_error_string(err, 0); // error:04075070:lib(4):func(117):reason(112)
delete [] signature;
[...]
}
What am I doing wrong in the code?
check out this SO answer. rsautl is depreciated in favor of pkeyutl
Essentially, RSA signing should use RSA-PSS signature padding scheme to be secure, and the RSA-PSS will be strictly larger than the digest, because of the necessity to communicate a salt. additionally, RSA_sign does a digest as a part of the signing scheme, so your code is going to do a digest of a digest. you want to instead either pass the message in directly, or use RSA_private_encrypt combined with a suitable RSA_padding_add_x call.
and as I said in my comment, a 256-bit key is sufficiently secure for a symmetric algorithm, but it will be trivial to crack for an RSA private key (on the order of 4 minutes on a couple of year old machine). Using a bare minimum of a 1024 bit key (and probably a 2048 or 4096 bit key) will give you a security level roughly equivalent to a 128 bit symmetric algorithm.
Re Peter Elliott: not quite.
The PKCS1v1_5 standard sequence in RFC8017 section 8.2.1 (and 9.2, similar in rfc4347 and rfc3447) is hash, encode in ASN.1, pad ('type 1') and modexp d. (PSS omits the ASN.1 step and uses different padding.)
OpenSSL (low-level) RSA_sign does the last three (with v1.5 padding) but not the first -- i.e. it encodes the hash but it doesn't do the hash; even-lower RSA_private_encrypt does the last two.
The higher-level envelope module (original EVP_Sign{Init,Update,Final}*, enhanced EVP_DigestSign*) does all four.
It's the ASN.1 encode that made the value too large for a 256-bit key -- which as stated is breakable anyway.
rsautl does only the last two steps not the encode, so it runs but gives a nonstandard result.
pkeyutl for RSA does the same by default but can be told to do the encode.
Also, PSS has a better security proof, but as far as I've heard there's no actual attack on v1.5 and it's still widely used.
If you have the choice of PSS (both/all parties support it) choose it, but don't feel worried about using v1.5.
Guess I found the solution. openssl rsautl -sign uses RSA_private_encrypt instead of RSA_sign (what I would have expected). RSA_sign creates a longer structure than the 20-bytes message I provided, so it fails with the given error.