sharplispers / ironclad

A cryptographic toolkit written in Common Lisp
BSD 3-Clause "New" or "Revised" License
166 stars 28 forks source link

Add new generic function for destructuring EC points #61

Closed dnaeon closed 1 year ago

dnaeon commented 1 year ago

This PR adds a new generic function - EC-DESTRUCTURE-POINT and provides implementation for the existing EC point classes.

Also, it exports the various EC point operations and adds slot readers.

glv2 commented 1 year ago

The EC-DESTRUCTURE-POINT function as you wrote it will return the internal representation of points, which is not the regular (x,y) coordinates of a point on the curve. Internally we're using projective coordinates which make calculations much faster by avoiding point divisions. A single (x, y) point can have many different but equivalent (X, Y, Z) projective coordinates.

Do you really need to know the internal representation of points, or is it the "official" curve coordinates that you are interested in?

dnaeon commented 1 year ago

Hey @glv2 ,

Thanks for the quick reply!

I have this example JWK public key.

{
    "kty": "EC",
    "use": "sig",
    "crv": "P-256",
    "kid": "test-id",
    "x": "Nyp0YT9NRlY44cULzFiGnWzF_BsaOwyxmsukKkbvuKA",
    "y": "joRM15S51FU8NNwMpN03mndkxNcK_5cTOn8bVEBy9KU",
    "alg": "ES256"
}

In order to decode above key I'm using the following code.

(defun decode-secp256r1-pk (data)
  "Decodes Secp256r1 (NIST P-256) public key from the given plist data.
See RFC 7518, Section 6.2.1 for more details about Elliptic Curve
public keys format."
  (let* ((use (getf data :|use|))
         (alg (getf data :|alg|))
         (kid (getf data :|kid|))
         (kty (getf data :|kty|))
         (key-ops (getf data :|key_ops|))
         (crv (getf data :|crv|))
         (x (getf data :|x|))
         (y (getf data :|y|)))
    (unless x
      (error 'invalid-key :message "Missing X coordinate parameter" :data data))
    (unless y
      (error 'invalid-key :message "Missing Y coordinate parameter" :data data))
    ;; The X and Y coordinates are Base64urlUInt-encoded values
    (let* ((x-octets (binascii:decode-base64url x))
           (y-octets (binascii:decode-base64url y))
           (x-uint (ironclad:ec-decode-scalar :secp256r1 x-octets))
           (y-uint (ironclad:ec-decode-scalar :secp256r1 y-octets))
           (point (make-instance 'ironclad:secp256r1-point :x x-uint :y y-uint :z 1)))
      (unless (and (= (length x-octets) 32)
                   (= (length y-octets) 32))
        (error 'invalid-key :message "Coordinates should be 32 bytes for Secp256r1 key" :data data))
      (list :use use
            :alg alg
            :kid kid
            :kty kty
            :key-ops key-ops
            :crv crv
            :key (ironclad:make-public-key :secp256r1 :y (ironclad:ec-encode-point point))))))

Then on the REPL:

CL-USER> (defparameter *key-plist*
       (jonathan:parse (uiop:read-file-string #P"/path/to/ec-public-key.json") :as :plist))
*KEY-PLIST*

CL-USER> (defparameter *parsed-key* (decode-secp256r1-pk *key-plist*))
*PARSED-KEY*

Now, in order to encode back the *PARSED-KEY* into JWK format, I need to add x and y coordinates in the resulting JSON.

The only way I could do that so far is if get y using (ironclad:secp256r1-key-y (getf *parsed-key* :key), then use (ironclad:ec-decode-point :secp256r1 y) in order to get the point.

And finally I could use (ironclad:ec-destructure-point point) in order to get x and y coords, which will then be encoded in the resulting JWK JSON.

Is there another way I should be approaching this, or is this the way to go?

Thanks!

glv2 commented 1 year ago

I added ec-make-point and ec-destructure-point functions (5a467eb18475a577cb532b485f2230c65eff7eeb) containing the appropriate coordinate conversions. I also added a modified version of the second patch of this pull request, to export EC-related functions (2da4f5ea6f2638d64b24e4da25b048a115d86308).

So now you should be able to do:

(let* ((coordinates (ec-destructure-point p))
       (x-uint (getf coordinates :x))
       (y-uint (getf coordinates :y))
  ...)

(ec-make-point :secp256r1 :x x-uint :y y-uint)

Could you give it a try to see if something is missing?

dnaeon commented 1 year ago

Hey @glv2 ,

I'll try it out soon and get back to you.

Thanks!

dnaeon commented 1 year ago

@glv2 , everything seems to work fine, thanks!