Skip to content

Commit

Permalink
Add an internal ecdsa package with ECDSA encoding functions
Browse files Browse the repository at this point in the history
This is a prerequisite to add a new "full" primitive type for ECDSA that will replace `subtle.{ECDSASigner,ECDSAVerifier}`. A future change will use this in the `signature/subtle` package.

PiperOrigin-RevId: 695645378
Change-Id: Ic91a621a2d6649628c6cab08b332330fd71bacaf
  • Loading branch information
morambro authored and copybara-github committed Nov 12, 2024
1 parent 1a5e215 commit b2edbf0
Show file tree
Hide file tree
Showing 2 changed files with 361 additions and 0 deletions.
105 changes: 105 additions & 0 deletions internal/signature/ecdsa/encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package ecdsa provides internal ECDSA utility functions.
package ecdsa

import (
"bytes"
"crypto/elliptic"
"encoding/asn1"
"fmt"
"math/big"
)

// Signature is an ECDSA signature.
type Signature struct {
R, S *big.Int
}

// ASN1Encode encodes the given ECDSA signature using ASN.1 encoding.
func ASN1Encode(s *Signature) ([]byte, error) {
ret, err := asn1.Marshal(*s)
if err != nil {
return nil, fmt.Errorf("asn.1 encoding failed")
}
return ret, nil
}

var errAsn1Decoding = fmt.Errorf("asn.1 decoding failed")

// ASN1Decode verifies the given ECDSA signature and decodes it if it is valid.
//
// Since asn1.Unmarshal() doesn't do a strict verification on its input, it will
// accept signatures with trailing data. Thus, we add an additional check to make sure
// that the input follows strict DER encoding: after unmarshalling the signature bytes,
// we marshal the obtained signature object again. Since DER encoding is deterministic,
// we expect that the obtained bytes would be equal to the input.
func ASN1Decode(b []byte) (*Signature, error) {
// parse the signature
sig := new(Signature)
_, err := asn1.Unmarshal(b, sig)
if err != nil {
return nil, errAsn1Decoding
}
// encode the signature again
encoded, err := asn1.Marshal(*sig)
if err != nil {
return nil, errAsn1Decoding
}
if !bytes.Equal(b, encoded) {
return nil, errAsn1Decoding
}
return sig, nil
}

func ieeeSignatureSize(curveName string) (int, error) {
switch curveName {
case elliptic.P256().Params().Name:
return 64, nil
case elliptic.P384().Params().Name:
return 96, nil
case elliptic.P521().Params().Name:
return 132, nil
default:
return 0, fmt.Errorf("ieeeP1363 unsupported curve name: %q", curveName)
}
}

// IEEEP1363Encode converts the signature to the IEEE_P1363 encoding format.
func IEEEP1363Encode(sig *Signature, curveName string) ([]byte, error) {
sigSize, err := ieeeSignatureSize(curveName)
if err != nil {
return nil, err
}
// Bounds checking for the FillBytes() calls.
scalarSize := sigSize / 2
if sig.R.BitLen() > scalarSize*8 || sig.S.BitLen() > scalarSize*8 {
return nil, fmt.Errorf("ecdsa: invalid signature")
}
enc := make([]byte, sigSize)
sig.R.FillBytes(enc[:sigSize/2])
sig.S.FillBytes(enc[sigSize/2:])
return enc, nil
}

// IEEEP1363Decode decodes the given ECDSA signature using IEEE_P1363 encoding.
func IEEEP1363Decode(encodedBytes []byte) (*Signature, error) {
if len(encodedBytes) == 0 || len(encodedBytes) > 132 || len(encodedBytes)%2 != 0 {
return nil, fmt.Errorf("ecdsa: Invalid IEEE_P1363 encoded bytes")
}
r := new(big.Int).SetBytes(encodedBytes[:len(encodedBytes)/2])
s := new(big.Int).SetBytes(encodedBytes[len(encodedBytes)/2:])
return &Signature{R: r, S: s}, nil
}
256 changes: 256 additions & 0 deletions internal/signature/ecdsa/encoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ecdsa_test

import (
"crypto/elliptic"
"encoding/hex"
"math/big"
"slices"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/tink-crypto/tink-go/v2/internal/signature/ecdsa"
)

func hexToBytes(t *testing.T, h string) []byte {
t.Helper()
b, err := hex.DecodeString(h)
if err != nil {
t.Fatalf("hex.DecodeString(%q) err = %v, want nil", h, err)
}
return b
}

func TestASN1Encode(t *testing.T) {
for _, tc := range []struct {
name string
rHex string
sHex string
derHex string
}{
{
name: "short form length",
rHex: "0102030405060708090a0b0c0d0e0f10",
sHex: "1102030405060708090a0b0c0d0e0fff",
derHex: "302402100102030405060708090a0b0c0d0e0f1002101102030405060708090a0b0c0d0e0fff",
},
{
name: "long form length",
rHex: "010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000203",
sHex: "0f0000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000204",
derHex: "308188024201000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000020302420f0000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000204",
},
{
name: "zero prefix",
rHex: "02030405060708090a0b0c0d0e0f10",
sHex: "02030405060708090a0b0c0d0e0f10",
derHex: "3022020f02030405060708090a0b0c0d0e0f10020f02030405060708090a0b0c0d0e0f10",
},
{
name: "highest bit set - long form length",
rHex: "ff02030405060708090a0b0c0d0e0f10",
sHex: "ff02030405060708090a0b0c0d0e0f10",
derHex: "3026021100ff02030405060708090a0b0c0d0e0f10021100ff02030405060708090a0b0c0d0e0f10",
},
} {
t.Run(tc.name, func(t *testing.T) {
s := &ecdsa.Signature{R: new(big.Int).SetBytes(hexToBytes(t, tc.rHex)), S: new(big.Int).SetBytes(hexToBytes(t, tc.sHex))}
got, err := ecdsa.ASN1Encode(s)
if err != nil {
t.Fatalf("ecdsa.ASN1Encode(%v) err = %v, want nil", s, err)
}
if diff := cmp.Diff(hexToBytes(t, tc.derHex), got); diff != "" {
t.Errorf("ecdsa.ASN1Encode(%v) returned unexpected diff (-want +got):\n%s", s, diff)
}
})
}
}

func TestASN1EncodeFails(t *testing.T) {
s := &ecdsa.Signature{}
if _, err := ecdsa.ASN1Encode(s); err == nil {
t.Fatalf("ecdsa.ASN1Encode(%v) err = nil, want error", s)
}
}

func TestASN1Decode(t *testing.T) {
s := &ecdsa.Signature{R: big.NewInt(1), S: big.NewInt(2)}
encoded, err := ecdsa.ASN1Encode(s)
if err != nil {
t.Fatalf("ecdsa.ASN1Encode(%v) err = %v, want nil", s, err)
}
got, err := ecdsa.ASN1Decode(encoded)
if err != nil {
t.Fatalf("ecdsa.ASN1Decode(%v) err = %v, want nil", encoded, err)
}
if got.R.Cmp(s.R) != 0 {
t.Errorf("ecdsa.ASN1Decode(%v).R = %v, want %v", encoded, got.R, s.R)
}
if got.S.Cmp(s.S) != 0 {
t.Errorf("ecdsa.ASN1Decode(%v).S = %v, want %v", encoded, got.S, s.S)
}
}

func TestASN1DecodeFails(t *testing.T) {
if _, err := ecdsa.ASN1Decode([]byte("invalid")); err == nil {
t.Fatalf("ecdsa.ASN1Decode(%v) err = nil, want error", err)
}
}

func TestIEEEP1363Encode(t *testing.T) {
// P-256 point.
p256x := hexToBytes(t, "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296")
p256y := hexToBytes(t, "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5")
// P-384 point.
p384x := hexToBytes(t, "aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7")
p384y := hexToBytes(t, "3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f")
// P-521 point.
p521x := hexToBytes(t, "c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66")
p521y := hexToBytes(t, "011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650")
for _, tc := range []struct {
name string
s *ecdsa.Signature
c string
want []byte
}{
{
name: "p256",
s: &ecdsa.Signature{R: new(big.Int).SetBytes(p256x), S: new(big.Int).SetBytes(p256y)},
c: "P-256",
want: slices.Concat(p256x, p256y),
},
{
name: "p384",
s: &ecdsa.Signature{R: new(big.Int).SetBytes(p384x), S: new(big.Int).SetBytes(p384y)},
c: "P-384",
want: slices.Concat(p384x, p384y),
},
{
name: "p521",
s: &ecdsa.Signature{R: new(big.Int).SetBytes(p521x), S: new(big.Int).SetBytes(p521y)},
c: "P-521",
want: slices.Concat([]byte{0x00}, p521x, p521y),
},
} {
t.Run(tc.name, func(t *testing.T) {
got, err := ecdsa.IEEEP1363Encode(tc.s, tc.c)
if err != nil {
t.Fatalf("ecdsa.IEEEP1363Encode(%v, %v) err = %v, want nil", tc.s, tc.c, err)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("ecdsa.IEEEP1363Encode(%v, %v) returned unexpected diff (-want +got):\n%s", tc.s, tc.c, diff)
}
})
}
}

func TestIEEEP1363EncodeFails(t *testing.T) {
for _, tc := range []struct {
name string
s *ecdsa.Signature
c string
}{
{
name: "invalid R",
s: &ecdsa.Signature{R: elliptic.P256().Params().Gx, S: new(big.Int).Lsh(big.NewInt(1), 256)},
c: "P-256",
},
{
name: "invalid S",
s: &ecdsa.Signature{R: new(big.Int).Lsh(big.NewInt(1), 256), S: elliptic.P256().Params().Gy},
c: "P-256",
},
{
name: "invalid curve name",
s: &ecdsa.Signature{R: elliptic.P256().Params().Gx, S: elliptic.P256().Params().Gy},
c: "invalid",
},
{
name: "wrong curve",
s: &ecdsa.Signature{R: elliptic.P384().Params().Gx, S: elliptic.P384().Params().Gy},
c: "P-256",
},
} {
t.Run(tc.name, func(t *testing.T) {
if _, err := ecdsa.IEEEP1363Encode(tc.s, tc.c); err == nil {
t.Errorf("ecdsa.IEEEP1363Encode(%v, %v) err = nil, want error", tc.s, tc.c)
}
})
}
}

func TestIEEEP1363Decode(t *testing.T) {
for _, tc := range []struct {
name string
rHex string
sHex string
ieeeHex string
}{
{
name: "16 bytes",
rHex: "0102030405060708090a0b0c0d0e0f10",
sHex: "1102030405060708090a0b0c0d0e0fff",
ieeeHex: "0102030405060708090a0b0c0d0e0f101102030405060708090a0b0c0d0e0fff",
},
{
name: "66 bytes",
rHex: "010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000203",
sHex: "0f0000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000204",
ieeeHex: "0100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000002030f0000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000204",
},
{
name: "30 bytes",
rHex: "02030405060708090a0b0c0d0e0f10",
sHex: "02030405060708090a0b0c0d0e0f10",
ieeeHex: "02030405060708090a0b0c0d0e0f1002030405060708090a0b0c0d0e0f10",
},
} {
t.Run(tc.name, func(t *testing.T) {
got, err := ecdsa.IEEEP1363Decode(hexToBytes(t, tc.ieeeHex))
if err != nil {
t.Fatalf("ecdsa.IEEEP1363Decode(%v) err = %v, want nil", tc.ieeeHex, err)
}
if want := new(big.Int).SetBytes(hexToBytes(t, tc.rHex)); got.R.Cmp(want) != 0 {
t.Errorf("ecdsa.IEEEP1363Decode(%v).R = %v, want %v", tc.ieeeHex, got.R, want)
}
if want := new(big.Int).SetBytes(hexToBytes(t, tc.sHex)); got.S.Cmp(want) != 0 {
t.Errorf("ecdsa.IEEEP1363Decode(%v).S = %v, want %v", tc.ieeeHex, got.S, want)
}
})
}
}

func TestIEEEP1363DecodeFails(t *testing.T) {
for _, tc := range []struct {
name string
encoded []byte
}{
{
name: "too small",
encoded: big.NewInt(1).Bytes(),
},
{
name: "too large",
encoded: new(big.Int).Lsh(big.NewInt(1), 132).Bytes(),
},
} {
t.Run(tc.name, func(t *testing.T) {
if _, err := ecdsa.IEEEP1363Decode(tc.encoded); err == nil {
t.Fatalf("ecdsa.IEEEP1363Decode(%v) err = nil, want error", tc.encoded)
}
})
}
}

0 comments on commit b2edbf0

Please sign in to comment.