-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an internal
ecdsa
package with ECDSA encoding functions
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
1 parent
1a5e215
commit b2edbf0
Showing
2 changed files
with
361 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |