IPFS ping protocol

I extracted the 294 bytes (those which start with 30 82 01 22 30). To recap, the bytes are found inside the secio handshake. The handshake is protobuf serialized Propose with 5 fields, of which the field pubkey ID=2 is again protobuf serialized PublicKey with 2 fields, of which the field with again ID=2 contains the public key in DER format.

Graphically:

   Response -> {nonce,
    	    	pubkey -> PublicKey {KeyType, 
    	    	    	    	     Data},
    	    	exchanges,
                ciphers,
                hashes
   }

Once you save these bytes to for example publickey.der, then you can run openssl to check if you exctracted correctly the public key data.

You do it by invoking the following command to CLI:

openssl rsa -text -inform DER -in publickey.der -pubin

For example, I’m giving what I got (you’ll get of course something else):

Public-Key: (2048 bit)
Modulus:
    00:99:00:93:c8:b3:23:f8:19:31:2a:c6:08:07:1e:
    97:ff:09:f3:76:f9:eb:86:28:c0:86:5f:54:53:8f:
    e3:a7:65:28:a6:51:43:42:7e:73:cb:0f:3f:1c:7a:
    96:2f:32:ef:1a:91:7a:b0:48:1b:d9:94:05:88:dd:
    95:f9:fb:70:91:2d:71:82:80:99:54:68:70:0d:e7:
    12:ef:bd:65:de:7f:38:94:b8:74:e5:49:8c:1b:8c:
    da:ef:6b:6d:c2:56:92:a3:6c:0a:56:30:26:d3:ad:
    07:87:37:a4:33:11:a0:83:65:85:5a:ca:f8:8a:1e:
    cf:63:7f:e0:19:92:cc:e0:00:01:29:5d:eb:9f:9f:
    cd:a1:fb:5d:ca:9a:26:70:7f:98:84:95:a7:0c:0f:
    39:bf:ff:f6:ee:42:a5:b9:4d:01:6a:3a:d1:1a:61:
    ad:cb:5b:69:a5:c0:22:a2:c4:d5:4d:17:94:da:d7:
    d3:fa:4b:6f:aa:c8:d5:09:eb:c7:85:cd:2d:fb:19:
    a1:1d:75:49:7a:37:5f:b8:fc:68:b6:79:b1:39:b5:
    1a:81:35:a7:07:b4:aa:0d:c1:b7:17:0c:cc:df:b2:
    2d:e3:6e:b4:a0:a8:17:58:c6:bd:c4:13:b5:dc:c0:
    23:8a:2e:d1:35:4f:bb:26:d1:a6:f6:c0:0c:45:5c:
    0a:d5
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmQCTyLMj+BkxKsYIBx6X
/wnzdvnrhijAhl9UU4/jp2UoplFDQn5zyw8/HHqWLzLvGpF6sEgb2ZQFiN2V+ftw
kS1xgoCZVGhwDecS771l3n84lLh05UmMG4za72ttwlaSo2wKVjAm060HhzekMxGg
g2WFWsr4ih7PY3/gGZLM4AABKV3rn5/NoftdypomcH+YhJWnDA85v//27kKluU0B
ajrRGmGty1tppcAiosTVTReU2tfT+ktvqsjVCevHhc0t+xmhHXVJejdfuPxotnmx
ObUagTWnB7SqDcG3FwzM37It4260oKgXWMa9xBO13MAjii7RNU+7JtGm9sAMRVwK
1QIDAQAB
-----END PUBLIC KEY-----

From what I understood insofar, you have to answer in the same way, but in order to do that you have to generate first a new public/private key (and nonce, 16 random bytes).

To generate the pair we can use openssl.

We generate first the private key.
openssl genrsa -out client-pk.pem

From the private key we can generate the public key in DER format.
openssl rsa -in clien-pk.pem -pubout -outform DER > client-pubk.der

When you answer you get a response in a similar way than before.
Again first 4 bytes are the length of the message, followed by protobuf serialized data Response.

This time we have just 2 fields, both of type 2 (string, bytes[]).

The first field (ID=1) is the so called ephemeral public key, which is the public key of a key pair, that you generate each time you form a new connection. You do this so that even if someone breaks the key, it broke only the session, not all the communication before, but you do use the same key for establishing who you are (peerID).

This key is supposed to be in P-256 eliptical form, but I’m not sure what that is. I suspect it’s secp256k1 or prime256v1. I think NIST named P-256 the later.

For some reason, the field is 65 bytes long and the first byte is always 0x04, the rest being the actual key bytes.

If I generate new eliptical keys using openssl:

openssl ecparam -name secp256k1 -genkey -noout -out ec-key.pem
openssl ec -in ec-key.pem -pubout -outform der -out ec-pub.der

I get the public key file ec-pub.der with 88 bytes (91 bytes with prime256v1).

Each time I generate new keys, the first 24 bytes (27 bytes with prime256v1) are always the same and the last 64 bytes are different. The byte before the bytes of the key is always 0x04, so it might be that it’s just a badly stripped header byte or it indicates the type of key. No idea.

The second field (ID=2) in the protobuf data is the signature. It’s 256 bytes long. It’s a SHA-256 RSA digest of the corpus in secio lingo.

The corpus is composed of 3 strings concatenated together:

  • Propose protobuf serialized data of the server (i.e. what we’ve received before Response, without the first 4 bytes indicating length)
  • Propose protobuf serialized data of the client (i.e. what we’ve sent back, without the first 4 bytes indicating length)
  • Ephemeral public key (i.e. the 65 bytes we just received)

We can check if the signature coresponds to the corpus if we save the corpus as described to a file e.g. corpus.dat, the signature to e.g. sig.dat and the public key of the server (which we received in the previous message, i.e. Propose) to server-pubkey.der and then running the following:

openssl dgst -sha256 -verify server-pubkey.der -keyform der -signature sig.dat corpus.dat

It should return Verified OK.

You respond to this the same way, just that in the corpus you reverse the order of client/server and use your own ephemeral public key, i.e. you concatenate your data Response, their data Response, your ephemeral public key, which you have to generate. I think you generate it as I described before, i.e. ec-pub.der, and you just take the last 65 bytes. You should use prime256v1, otherwise it doesn’t work.

We save this contatenation to a file e.g. `corpus2.dat.

Now we sign this using our private key.

openssl dgst -sha256 -sign client-private-key.pem -out signature.dat corpus2.dat'

The signature is in the file signature.dat, which will be included now in our protobuf response.

We protobuf serialize the data, so that ID=1 contains the 65 bytes of the generated ephemeral public key and ID=2 contains 256 bytes of the contents of signature.dat. Both fields are of type 2.

Now we send this back, together with 4 bytes at the begining indicating length of message.

We get the response, again with 4 leading bytes with the lenght anda 48 bytes response, which is everytime different.

It is supposed to be the received nonce, but now encrypted (16 bytes) and the SHA256 HMAC of the encrypted nonce (32 bytes).

Unforunately I got kinda stuck here, how to proceed. How it’s even encrypted, how is the HMAC generated, i.e. what does it use for the shared secret.