Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: atchops_rsa_generate #409

Merged
merged 9 commits into from
Sep 28, 2024
Merged

feat: atchops_rsa_generate #409

merged 9 commits into from
Sep 28, 2024

Conversation

JeremyTubongbanua
Copy link
Member

@JeremyTubongbanua JeremyTubongbanua commented Sep 23, 2024

#closes #74

- What I did

  • Implemented atchops_rsa_generate
  • made it so that it takes the PKCS#1 formatted private RSA-2048 key from MbedTLS and we convert it ourselves into a PKCS#8 formatted private RSA-2048 key
  • Added a test for rsa generate

- How to verify it

  • Added a test test_rsa_generate.c that does two things: 1. generate a RSA key pair and populate our atchops structs successfully and 2. use the key to encrypt/decrypt and check if the plaintext matches
  • All previous rsa tests pass

- Description for the changelog
feat: atchops_rsa_generate

@JeremyTubongbanua JeremyTubongbanua self-assigned this Sep 23, 2024
@JeremyTubongbanua
Copy link
Member Author

JeremyTubongbanua commented Sep 23, 2024

I have reached a blocker...

Right now, MbedTLS only supports writing PKCS#1 formatted RSA 2048 private keys.

This is what a PKCS 1 private key looks like (ASN.1 decoded). Link
image

However, the goal of atchops_rsa_generate is to write PKCS#8 formatted RSA 2048 private keys...

This is what a PKCS#8 formatted private key looks like (ASN.1 decoded). Link
image

Currently, our Dart client writes PKCS#8 formatted keys.

I did some research and came across this raised issue that is still open, which addresses the issue that PKCS#8 formatted private keys can be parsed, but not written. There was even a PR opened to implement PKCS#8 writing, but was never merged. The PR was opened in 2019. So in summary, writing PKCS#8 formatted RSA-2048 private keys is a known issue, which has a fix to it, but hasn't been merged yet by the MbedTLS team.

There are two possible things to do here:

  1. Add support for PKCS#1 formatted RSA-2048 private keys in both our C SDK and Dart SDK. Currently, the C SDK only supports parsing PKCS#8 formatted keys (which means that it would not be able to read a key that is genearted by the C SDK). And our Dart SDK (I still have to check) can parse PKCS#8 formatted keys , and am still unsure if it can parse PKCS#1 formatted keys.
  2. Maintain our own MbedTLS fork with the PKCS#8 writing feature PR merged to it (which I am very not inclined in doing).

@JeremyTubongbanua
Copy link
Member Author

https://gist.github.com/JeremyTubongbanua/bc88bc7a818f7e5176d28a1975661e8f

I got some progress in adding the proper PKCS#8 headers to the key, but not quite there yet

@realvarx
Copy link
Contributor

I have reached a blocker...

Right now, MbedTLS only supports writing PKCS#1 formatted RSA 2048 private keys.

This is what a PKCS 1 private key looks like (ASN.1 decoded). Link image

However, the goal of atchops_rsa_generate is to write PKCS#8 formatted RSA 2048 private keys...

This is what a PKCS#8 formatted private key looks like (ASN.1 decoded). Link image

Currently, our Dart client writes PKCS#8 formatted keys.

I did some research and came across this raised issue that is still open, which addresses the issue that PKCS#8 formatted private keys can be parsed, but not written. There was even a PR opened to implement PKCS#8 writing, but was never merged. The PR was opened in 2019. So in summary, writing PKCS#8 formatted RSA-2048 private keys is a known issue, which has a fix to it, but hasn't been merged yet by the MbedTLS team.

There are two possible things to do here:

  1. Add support for PKCS#1 formatted RSA-2048 private keys in both our C SDK and Dart SDK. Currently, the C SDK only supports parsing PKCS#8 formatted keys (which means that it would not be able to read a key that is genearted by the C SDK). And our Dart SDK (I still have to check) can parse PKCS#8 formatted keys , and am still unsure if it can parse PKCS#1 formatted keys.
  2. Maintain our own MbedTLS fork with the PKCS#8 writing feature PR merged to it (which I am very not inclined in doing).

We could also integrate an open source parser into at_chops or develop one ourselves. Imo, that would be the most reasonable decision. If I remember correctly, we have already handled DER formatting successfully in at_pico_w

@JeremyTubongbanua
Copy link
Member Author

I have reached a blocker...
Right now, MbedTLS only supports writing PKCS#1 formatted RSA 2048 private keys.
This is what a PKCS 1 private key looks like (ASN.1 decoded). Link image
However, the goal of atchops_rsa_generate is to write PKCS#8 formatted RSA 2048 private keys...
This is what a PKCS#8 formatted private key looks like (ASN.1 decoded). Link image
Currently, our Dart client writes PKCS#8 formatted keys.
I did some research and came across this raised issue that is still open, which addresses the issue that PKCS#8 formatted private keys can be parsed, but not written. There was even a PR opened to implement PKCS#8 writing, but was never merged. The PR was opened in 2019. So in summary, writing PKCS#8 formatted RSA-2048 private keys is a known issue, which has a fix to it, but hasn't been merged yet by the MbedTLS team.
There are two possible things to do here:

  1. Add support for PKCS#1 formatted RSA-2048 private keys in both our C SDK and Dart SDK. Currently, the C SDK only supports parsing PKCS#8 formatted keys (which means that it would not be able to read a key that is genearted by the C SDK). And our Dart SDK (I still have to check) can parse PKCS#8 formatted keys , and am still unsure if it can parse PKCS#1 formatted keys.
  2. Maintain our own MbedTLS fork with the PKCS#8 writing feature PR merged to it (which I am very not inclined in doing).

We could also integrate an open source parser into at_chops or develop one ourselves. Imo, that would be the most reasonable decision. If I remember correctly, we have already handled DER formatting successfully in at_pico_w

MbedTLS comes with their own ASN.1 Parser that we currently use, if that's what you're referring to. So I don't think we have to develop any parsers ourselves (please let me know if I am misunderstanding).

@JeremyTubongbanua JeremyTubongbanua changed the title feat: (WIP) atchops_rsa_generate feat: atchops_rsa_generate Sep 26, 2024
@JeremyTubongbanua
Copy link
Member Author

This is the hack that I produced:

I hard coded the PKCS 8 headers to the key. Then simply copy and pasted the PKCS#1 key since the PKCS#1 is a subset of the entire PKCS#8 key.

// PrivateKeyInfo SEQUENCE (3 elements)
private_key_pkcs8[0] = 0x30; // constructed sequence tag
private_key_pkcs8[1] = 0x82; // 8 --> 1000 0000 (1 in MSB means that it is long form) and 2 --> 0010 0000 (the next 2
                             // bytes are the length of data)
private_key_pkcs8[2] = (unsigned char)((private_key_pkcs8_size >> 8) & 0xFF);
private_key_pkcs8[3] = (unsigned char)(private_key_pkcs8_size & 0xFF);
// version INTEGER 0
private_key_pkcs8[4] = 0x02; // integer tag
private_key_pkcs8[5] = 0x01; // length of data
private_key_pkcs8[6] = 0x00; // data
// private key algorithm identifier
private_key_pkcs8[7] = 0x30; // constructed sequence tag
private_key_pkcs8[8] = 0x0D; // there are 2 elements in the sequence
private_key_pkcs8[9] = 0x06;
private_key_pkcs8[10] = 0x09;
private_key_pkcs8[11] = 0x2A;
private_key_pkcs8[12] = 0x86;
private_key_pkcs8[13] = 0x48;
private_key_pkcs8[14] = 0x86;
private_key_pkcs8[15] = 0xF7;
private_key_pkcs8[16] = 0x0D;
private_key_pkcs8[17] = 0x01;
private_key_pkcs8[18] = 0x01;
private_key_pkcs8[19] = 0x01;
private_key_pkcs8[20] = 0x05;
private_key_pkcs8[21] = 0x00;
// PrivateKey OCTET STRING
private_key_pkcs8[22] = 0x04; // octet string tag
private_key_pkcs8[23] = 0x82; // 8 --> 1000 0000 (1 in MSB means that it is long form) and 2 --> 0010 0000 (the next 2
                              // bytes are the length of data)
private_key_pkcs8[24] = (unsigned char)((private_key_non_base64_len >> 8) & 0xFF); // length of data
private_key_pkcs8[25] = (unsigned char)(private_key_non_base64_len & 0xFF);        // length of data

@JeremyTubongbanua
Copy link
Member Author

This is the unit test that successfully passes:

atchops_rsa_key_public_key public_key;
atchops_rsa_key_public_key_init(&public_key);
atchops_rsa_key_private_key private_key;
atchops_rsa_key_private_key_init(&private_key);
const size_t ciphertext_size = 256;
unsigned char ciphertext[ciphertext_size];
memset(ciphertext, 0, sizeof(unsigned char) * ciphertext_size);
const size_t plaintext_size = 256;
unsigned char plaintext[plaintext_size];
memset(plaintext, 0, sizeof(unsigned char) * plaintext_size);
if ((ret = atchops_rsa_key_generate(&public_key, &private_key)) != 0) {
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_ERROR, "Failed to generate RSA key pair\n");
  goto exit;
}
// log the public key
if (atchops_rsa_key_is_public_key_populated(&public_key)) {
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "Public Key:\n");
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "N: ");
  for (size_t i = 0; i < public_key.n.len; i++) {
    printf("%02x ", public_key.n.value[i]);
  }
  printf("\n");
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "E: ");
  for (size_t i = 0; i < public_key.e.len; i++) {
    printf("%02x ", public_key.e.value[i]);
  }
  printf("\n");
} else {
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_ERROR, "Public key is not populated\n");
  goto exit;
}
// log the private key
if (atchops_rsa_key_is_private_key_populated(&private_key)) {
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "Private Key:\n");
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "N: ");
  for (size_t i = 0; i < private_key.n.len; i++) {
    printf("%02x ", private_key.n.value[i]);
  }
  printf("\n");
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "E: ");
  for (size_t i = 0; i < private_key.e.len; i++) {
    printf("%02x ", private_key.e.value[i]);
  }
  printf("\n");
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "D: ");
  for (size_t i = 0; i < private_key.d.len; i++) {
    printf("%02x ", private_key.d.value[i]);
  }
  printf("\n");
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "P: ");
  for (size_t i = 0; i < private_key.p.len; i++) {
    printf("%02x ", private_key.p.value[i]);
  }
  printf("\n");
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "Q: ");
  for (size_t i = 0; i < private_key.q.len; i++) {
    printf("%02x ", private_key.q.value[i]);
  }
  printf("\n");
} else {
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_ERROR, "Private key is not populated\n");
  goto exit;
}
// use the public key to encrypt something
// use the private key to decrypt it
if((ret = atchops_rsa_encrypt(&public_key, (const unsigned char *)PLAINTEXT, strlen(PLAINTEXT), ciphertext)) != 0) {
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_ERROR, "Failed to encrypt plaintext\n");
  goto exit;
}
// log the ciphertext
atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "Ciphertext: ");
for (size_t i = 0; i < ciphertext_size; i++) {
  printf("%02x ", ciphertext[i]);
}
  
size_t plaintext_len = 0;
if((ret = atchops_rsa_decrypt(&private_key, ciphertext, ciphertext_size, plaintext, plaintext_size, &plaintext_len)) != 0) {
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_ERROR, "Failed to decrypt ciphertext\n");
  goto exit;
}
// log the plaintext
atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_INFO, "Plaintext: ");
for (size_t i = 0; i < plaintext_len; i++) {
  printf("%c", plaintext[i]);
}
printf("\n");
// check if plaintext is equal to PLAINTEXT
if (strncmp((const char *)plaintext, PLAINTEXT, strlen(PLAINTEXT)) != 0) {
  atlogger_log("test_rsa_generate", ATLOGGER_LOGGING_LEVEL_ERROR, "Plaintext does not match original\n");
  goto exit;
}

We

  1. generate a keypair
  2. use it to encrypt/decrypt
  3. check if the result matches to expected value

@JeremyTubongbanua JeremyTubongbanua marked this pull request as ready for review September 26, 2024 16:47
@XavierChanth XavierChanth merged commit a314efb into trunk Sep 28, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

at_c: atchops_rsa_generate
3 participants