EvoMaster icon indicating copy to clipboard operation
EvoMaster copied to clipboard

Support for generating inputs dealing with RSA encryption.

Open ytfrank opened this issue 11 months ago • 17 comments

A classic scenario is that the other party first encrypts the data, signs all the business parameters with its own RSA private key, and then puts the value of this signature into the parameter "sign". Therefore, after receiving the request in the API, it will first verify whether the "sign" in the input parameters is correct and then decrypt it. If any step is wrong, API will return and the later codes can not be covered.

One example of verifying the sign: // java.security.Signature public static boolean verify(byte[] data, byte[] sign, PublicKey publicKey) throws Exception { Signature signature = Signature.getInstance("SHA256withRSA"); signature.initVerify(publicKey); signature.update(data); return signature.verify(sign); }

Other important info:

  • version of EvoMaster (EM) used : 3.3.0
  • how EM is run (eg, if from JAR or from one of its OS installers) : white-box
  • version of applicable runtimes (eg, JVM, NodeJS and .Net). For Java, can paste the output of java --version : 1.8.0
  • command-line options used to run EM

ytfrank avatar Jan 07 '25 16:01 ytfrank

hi @ytfrank , currently there is no way in EM to do something like this. it seems like a very special scenario. Haven't heard of doing something similar before in other APIs... or maybe it is something common in a specific domain / business? Automatically reversing/inferring RSA private keys is simply infeasible. Such info would have to be provided to EM, somehow. It could be done, but, to be honest, it is not a feature that we would prioritize, unless we see more APIs that require such a feature.
Such feature could be implemented as part of a so-called "academia-industry" collaboration (see here for details). If that is something that could be of interest, you can contact me at [email protected] (as GitHub does not have private message support)

arcuri82 avatar Jan 07 '25 20:01 arcuri82

Thanks for your info. In the field of financial credit, data encryption and signature before transmission are common practices. I'd like to study how to provide the info to EM first, and then evaluate the proper way.

ytfrank avatar Jan 09 '25 02:01 ytfrank

hi @ytfrank are there any example / proof-of-concept open-source APIs with these properties that I can look at? or is there any closed-source, industrial API that has its OpenAPI schema online that I can look at? with a concrete example, I could try to estimate how much work would be needed to add such a feature

arcuri82 avatar Jan 09 '25 12:01 arcuri82

Here is an OpenAPI example:

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
  - url: http://localhost:8080
paths:
  /api/bind_card_apply:
    post:
      operationId: bindCard_1
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Req'
        required: true
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Resp'
components:
  schemas:
    Req:
      type: object
      properties:
        appId:
          type: string
          description: Application ID
        data:
          type: string
          description: Encrypt data using AES symmetric encryption
        requestId:
          type: string
          description: Request ID
        timestamp:
          type: string
          description: Timestamp
        key:
          type: string
          description: The encrypted AES key, encrypted by the other party's RSA public key
        sign:
          type: string
          description: Signature of the data, used to verify the integrity of the request
        bizData:
          $ref: '#/components/schemas/BindCardReq'
          description: Plaintext data, used to store the request data after decryption
    BindCardReq:
      type: object
      properties:
        loanPersonName:
          type: string
          description: Borrower's name
          maxLength: 30
          minLength: 3
        bankCardNo:
          type: string
          description: Bank card number, used to bind the bank card
          maxLength: 19
          minLength: 16
          pattern: "^[0-9]+$" # Supports digits only
        bankCardPhoneNumber:
          type: string
          description: Bank card reserved phone number, used to receive verification codes
          maxLength: 12
          minLength: 10
    Resp:
      type: object
      properties:
        data:
          type: string
          description: Encrypt data using AES symmetric encryption
        key:
          type: string
          description: The encrypted AES key, encrypted by the other party's RSA public key
        sign:
          type: string
          description: Signature of the data, used to verify the integrity of the response
        bizData:
          $ref: '#/components/schemas/BindCardResp'
    BindCardResp:
      type: object
      properties:
        code:
          type: string
          description: Response status code, indicating the result of the request processing
        msg:
          type: string
          description: Response message, describing the details of the request processing
        sessionId:
          type: string
          description: session ID, used to track the verification code request
          maxLength: 64

Encryption Process The encryption process consists of the following steps:

  1. Generate an AES KEY.

  2. Encrypt the concatenated business parameters using the AES Key provided by the merchant to obtain a byte array.

AES(json parameters)

  1. Encode the byte array using Base64 to get a string, and use this string as the Params field.

Base64(AES(json parameters))

  1. Use the merchant's public key to encrypt the AES Key with RSA and then encode it with Base64. The encrypted AES Key will be used as the key field.

Base64(RSA(AES KEY))

After completing the above steps, we obtain the encrypted business parameters and secret key. Finally, these encrypted business parameter values are added to the system parameters.

Signature Process
Note: Signing is performed on the encrypted data.

  1. First, sort all parameters except sign in ascending order by letter, and then concatenate them using &.

appId=123&key=a&params=encryptData{"a":"b"}&timestamp=1&version=1.0

  1. Use the SHA1WithRSA algorithm and your private key to sign the concatenated string, resulting in a byte array.

SHA1WithRSA(appId=123&key=a&params=encryptData{"a":"b"}&timestamp=1&version=1.0)

  1. Encode the byte array using Base64URLSafe to obtain a signature string.

Base64URLSafe(SHA1WithRSA(appId=123&key=a&params=encryptData{"a":"b"}&timestamp=1&version=1.0))

After completing the above three steps, we obtain the signature of the business parameters. Finally, the signature value is added to the system parameter sign.

sign=Base64URLSafe(SHA1WithRSA(appId=123&key=a&params=encryptData{"a":"b"}&timestamp=1&version=1.0))

I hope there will be a mechanism to inform EM how to prepare specific data during initialization, and the specific implementation is coded by the user.

ytfrank avatar Jan 10 '25 03:01 ytfrank

hi @ytfrank ,

thanks for the explanation. Adding a feature to support this would be possible. In the driver, we could add a method to specify if any parameter is "derived" from others, and an abstract method to "infer" a derived field from the values of test case (eg JSON string representation of the object), for the user to implement. Then, each time we need to specify/mutate the value of a "derived" field, we call the "infer" method to create the actual value.

So yes, it is technically possible to add such feature. But, without APIs to use for experiments requiring such feature (e.g., open-source on GitHub, or in academia-industry collaborations), in all honesty it would be hard to prioritize this feature compared to other pressing ones :(

arcuri82 avatar Jan 13 '25 20:01 arcuri82

Thanks for your reply. It's somewhat difficult to access the company's code, so a demo open-source project has been prepared for testing: https://github.com/ytfrank/RestDemo

The openapi doc can be seen from http://localhost:8080/v3/api-docs after the app starts.

openapi: 3.0.1 info: title: OpenAPI definition version: v0 servers:

  • url: http://localhost:8080 description: Generated server url paths: /api/bind_card_apply: post: tags: - demo-controller operationId: bindCardApply requestBody: content: application/json: schema: $ref: '#/components/schemas/CommonReqBindCardReq' required: true responses: "200": description: OK content: '/': schema: $ref: '#/components/schemas/CommonRespBindCardResp' components: schemas: BindCardReq: required: - bankCardNo - bankCardPhoneNumber - idCardNo - loanPersonName type: object properties: idCardNo: maxLength: 18 minLength: 18 pattern: "^[0-9]{17}[0-9Xx]$" type: string loanPersonName: maxLength: 20 minLength: 0 type: string bankCardNo: maxLength: 19 minLength: 16 pattern: "^[0-9]+$" type: string bankCardPhoneNumber: maxLength: 11 minLength: 11 pattern: "^[1][3-9][0-9]{9}$" type: string CommonReqBindCardReq: type: object properties: appId: type: string data: type: string requestId: type: string timestamp: type: string key: type: string sign: type: string bizData: $ref: '#/components/schemas/BindCardReq' BindCardResp: type: object properties: code: type: string msg: type: string sessionId: type: string CommonRespBindCardResp: type: object properties: data: type: string key: type: string sign: type: string bizData: $ref: '#/components/schemas/BindCardResp'

ytfrank avatar Jan 21 '25 09:01 ytfrank

@ytfrank many thanks for providing a working example. I ll have a look at it. although this month is quite busy... might try to support this sometime during February. meanwhile, in that repository, could you specify the license of that code? (eg, if open-source license like Apache or LGPL) I might integrate parts of it inside EvoMaster for its own E2E tests to check this new functionality support (eg, look under e2e-tests folder, where we have hundreds of artificial APIs to test EvoMaster). alternative, if you want/prefer, you could make a PR of that API example into EvoMaster (eg, as new module under e2e-tests )

arcuri82 avatar Jan 22 '25 20:01 arcuri82

once we support this in EvoMaster, we might also consider to add it to https://github.com/WebFuzzing/EMB for experimentation

arcuri82 avatar Jan 22 '25 20:01 arcuri82

I just made a PR: https://github.com/WebFuzzing/EvoMaster/pull/1158

ytfrank avatar Jan 24 '25 05:01 ytfrank

@ytfrank thx! hopefully i ll manage to get sometime to support this by the end of February

arcuri82 avatar Jan 24 '25 08:01 arcuri82

You're welcome! Hope you can find time to support this. Let me know if you need anything.

ytfrank avatar Jan 24 '25 08:01 ytfrank

Hi! Any progress so far?

ytfrank avatar Mar 18 '25 01:03 ytfrank

Hi @ytfrank , unfortunately, there has been no progress :( We are stuck on some other high-priority tasks that are taking much longer than expected. Based on those, might manage to get it done during April, and hopefully no later than May

arcuri82 avatar Mar 18 '25 08:03 arcuri82

hi @ytfrank ,

I finally have sometime available to look into this feature request.

I have a question though. In the example you created, is bizData actually sent with the request? or is it expected to be left empty?

I mean, if it is sent with the request, wouldn't the controller (i.e., DemoController.bindCardApply) then need to check if the content of the decrypted data match what is inside bizData?

If it is not expected to be part of the request (ie its field bizData is left null, and only encrypted data is populated), would then the controller need to unmarshall the received decrypted data into a BindCardReq object using a JSON library?

arcuri82 avatar May 13 '25 20:05 arcuri82

I'm really glad to hear that you'll be able to review it.

Your guess is correct. bizData is not part of the request. The controller will put the decrypted data into bizData, and this operation is omitted in the demo code.

ytfrank avatar May 14 '25 01:05 ytfrank

hi @ytfrank ,

now in master branch we have support to handle this. to try it out, you ll need to build SNAPSHOT locally, as might take sometime before we make a new release. You can check e2e-tests/spring-rest-rsa/src/test/java/com/example/demo/controller/EmController.java, in particular there are 2 things to do:

  1. declare which parameters need to be derived, and their order, which is done in getProblemInfo()
  2. specify how parameters are derived (done by overriding deriveObjectParameterData())

the test e2e-tests/spring-rest-rsa/src/test/java/org/evomaster/e2etests/spring/rest/rsa/RsaEMTest.java shows that we can handle the example you created.

Two related notes:

  1. you REST controller was always returning 200... so made sure returning 400 in case of user error (make easier to check what is covered)
  2. the use of UUID.randomUUID() in the responses is problematic, as it makes the assertion flaky. we are aware of the issue, but will take sometime before we can address it properly

Let me know if this fixes your issues in using EvoMaster. thx

arcuri82 avatar May 23 '25 07:05 arcuri82

Great! The issue has been resolved. Thank you very much!

ytfrank avatar Jun 02 '25 13:06 ytfrank

closed as release 4.0.0 is out

arcuri82 avatar Aug 11 '25 21:08 arcuri82