From 8570cfd139997829d3bc4208fabea4b837a5381e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bl=C3=BCcher?= Date: Wed, 20 Nov 2024 12:37:31 +0100 Subject: [PATCH] fix: pad elliptic curve coordinates correctly in JWK representation --- create.go | 9 +++++++-- parse.go | 9 +++++++-- parse_test.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/create.go b/create.go index 58f14dc..41bb0af 100644 --- a/create.go +++ b/create.go @@ -54,9 +54,14 @@ type ed25519JWK struct { func reflect(v interface{}) (interface{}, error) { switch v := v.(type) { case *ecdsa.PublicKey: + // Calculate the size of the byte array representation of an elliptic curve coordinate + // and ensure that the byte array representation of the key is padded correctly. + bits := v.Curve.Params().BitSize + keyCurveBytesSize := bits/8 + bits%8 + return &ecdsaJWK{ - X: base64.RawURLEncoding.EncodeToString(v.X.Bytes()), - Y: base64.RawURLEncoding.EncodeToString(v.Y.Bytes()), + X: base64.RawURLEncoding.EncodeToString(v.X.FillBytes(make([]byte, keyCurveBytesSize))), + Y: base64.RawURLEncoding.EncodeToString(v.Y.FillBytes(make([]byte, keyCurveBytesSize))), Crv: v.Curve.Params().Name, Kty: "EC", }, nil diff --git a/parse.go b/parse.go index 86cb872..92a8ffd 100644 --- a/parse.go +++ b/parse.go @@ -278,11 +278,16 @@ func getKeyStringRepresentation(key interface{}) ([]byte, error) { var keyParts interface{} switch key := key.(type) { case *ecdsa.PublicKey: + // Calculate the size of the byte array representation of an elliptic curve coordinate + // and ensure that the byte array representation of the key is padded correctly. + bits := key.Curve.Params().BitSize + keyCurveBytesSize := bits/8 + bits%8 + keyParts = map[string]interface{}{ "kty": "EC", "crv": key.Curve.Params().Name, - "x": base64.RawURLEncoding.EncodeToString(key.X.Bytes()), - "y": base64.RawURLEncoding.EncodeToString(key.Y.Bytes()), + "x": base64.RawURLEncoding.EncodeToString(key.X.FillBytes(make([]byte, keyCurveBytesSize))), + "y": base64.RawURLEncoding.EncodeToString(key.Y.FillBytes(make([]byte, keyCurveBytesSize))), } case *rsa.PublicKey: keyParts = map[string]interface{}{ diff --git a/parse_test.go b/parse_test.go index df2b7f0..1e5bea1 100644 --- a/parse_test.go +++ b/parse_test.go @@ -37,6 +37,9 @@ const ( missingJWKHeader_proof = "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJpYXQiOjE2ODYxNDc4MzMsImp0aSI6IlRIaF82Sml3RWNFMEk4NFVGMVNPX3hOc09IY2pld29WcVpHUHhodmcycUUiLCJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20vdG9rZW4ifQ.XlZ2VbVx4qPwuuJrUHTZG5Bm7KKRGjwcdWBWuOiYdrdvEIR3W62bB2xqI9QqSU6XoyjTlb6DfY1865UDnGzbQA" // Currently signed by a OCT alg, needs to be changed once OCP is supported unsupportedKeyAlg_proof = "eyJhbGciOiJIUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6Im9jdCIsImtpZCI6IjBhZmVlMTQyLWEwYWYtNDQxMC1hYmNjLTlmMmQ0NGZmNDViNSIsImFsZyI6IkhTMjU2IiwiayI6IkZkRllGekVSd0MydUNCQjQ2cFpRaTRHRzg1THVqUjhvYnQtS1dSQklDVlEifX0.eyJpYXQiOjE4OTM0NTI0MDAsImp0aSI6IlZ2cTdTMTZBUUwwVEkzdWZiSWFabEtYS0FkdjU0dkVhQ3JyVjJUa0lBbDQiLCJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20vdG9rZW4ifQ.UEIBBDOkv_NberiIX0w4TiHnwOCQ5XXXidXdyv8JjpA" + + validES256LeadingZeroes_proof = "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwieCI6IkFCYjNFYXJRMEhMY2NGeUZtVC1TZUw0TktnMTdQZThzeENaZVlFbG1EVG8iLCJ5IjoiNWtIUWh6ZThWN2ZIdE1tYk82N0tiQ3NOdFRWaERPRlpUTTBZV3RTZUZFOCIsImNydiI6IlAtMjU2In19.eyJpYXQiOjE3MzIwODc2MDYsImp0aSI6IjJmYWJhYTYxLWU1MWEtNGNjYy05ZjA0LTg1NjRkMzA1N2UxMCIsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbS90b2tlbiIsImF0aCI6IlR0aDhubVZaT09UbVhqRDZDQkl5YVhOQ1pzb3hlUWdxNFZpaEdQTnNMdXMifQ.8RygRxPPK5M3gxtqarXCTvSBt5djhZ0b_0JD5U1ZwmCUflSk7nt5g_ilkWDZf2xflWuZhgeIFvkuazaLSKJuXw" + validES256LeadingZeroes_ath = "MEhdRysfC6YMBxMtlBzyLwTWHmLLusOkEh_ofH9GPjs" ) // Test that a malformed tokenString is rejected @@ -831,3 +834,32 @@ func TestParse_ProofWithExtraKeyMembersOKT(t *testing.T) { } } + +func TestParse_ProofWithLeadingZeroesEC(t *testing.T) { + // Arrange + httpUrl := url.URL{ + Scheme: "https", + Host: "server.example.com", + Path: "/token", + } + duration := time.Duration(438000) * time.Hour + opts := dpop.ParseOptions{ + Nonce: "", + TimeWindow: &duration, + } + + // Act + proof, err := dpop.Parse(validES256LeadingZeroes_proof, dpop.POST, &httpUrl, opts) + + // Assert + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if proof == nil || proof.Valid != true { + t.Errorf("Expected token to be valid") + } + + if proof.HashedPublicKey != validES256LeadingZeroes_ath { + t.Errorf("Expected hashed public key to be %v, got %v", validES256LeadingZeroes_ath, proof.HashedPublicKey) + } +}