From 6a91e5cd9e7060177b48f27de6cba2330d3045aa Mon Sep 17 00:00:00 2001 From: Dominik Roos Date: Thu, 10 Sep 2020 11:54:02 +0200 Subject: [PATCH] signed: add associated data In order to sign path segments, we need the concept of associated data, i.e., data that is covered by the signature, but is not included in the header and body. --- go/lib/scrypto/signed/BUILD.bazel | 6 +- go/lib/scrypto/signed/example_test.go | 163 ++++++++++++++++++++++ go/lib/scrypto/signed/export_test.go | 40 ++++++ go/lib/scrypto/signed/msg.go | 71 +++++++--- go/lib/scrypto/signed/msg_test.go | 190 +++++++++++++++++++++++--- go/pkg/proto/crypto/signed.pb.go | 62 +++++---- proto/crypto/v1/signed.proto | 7 +- 7 files changed, 475 insertions(+), 64 deletions(-) create mode 100644 go/lib/scrypto/signed/example_test.go create mode 100644 go/lib/scrypto/signed/export_test.go diff --git a/go/lib/scrypto/signed/BUILD.bazel b/go/lib/scrypto/signed/BUILD.bazel index 92d254a7fa..9409ad7077 100644 --- a/go/lib/scrypto/signed/BUILD.bazel +++ b/go/lib/scrypto/signed/BUILD.bazel @@ -19,7 +19,11 @@ go_library( go_test( name = "go_default_test", - srcs = ["msg_test.go"], + srcs = [ + "example_test.go", + "export_test.go", + "msg_test.go", + ], embed = [":go_default_library"], deps = [ "//go/pkg/proto/crypto:go_default_library", diff --git a/go/lib/scrypto/signed/example_test.go b/go/lib/scrypto/signed/example_test.go new file mode 100644 index 0000000000..3a0766888c --- /dev/null +++ b/go/lib/scrypto/signed/example_test.go @@ -0,0 +1,163 @@ +// Copyright 2020 Anapaya Systems +// +// 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 signed_test + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "fmt" + "time" + + "github.com/scionproto/scion/go/lib/scrypto/signed" + cryptopb "github.com/scionproto/scion/go/pkg/proto/crypto" +) + +func ExampleSign_basic() { + // Choose private key. + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + panic(err) + } + + // Define message to sign. + hdr := signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA512, + Timestamp: time.Now(), + } + body := []byte("very important message") + + // Sign the message. + signedMsg, err := signed.Sign(hdr, body, privateKey) + if err != nil { + panic(err) + } + + // Extract body without verification. Usually, you will not need this operation. + unverifiedBody, err := signed.ExtractUnverifiedBody(signedMsg) + if err != nil { + panic(err) + } + fmt.Println(string(unverifiedBody)) + // Output: + // very important message +} + +func ExampleSign_withAssociatedData() { + // Choose private key. + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + panic(err) + } + + // Define message to sign. + hdr := signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA512, + AssociatedDataLength: 8, + } + body := []byte("very important message") + associatedData := [][]byte{[]byte("more"), []byte("data")} + + // Sign the message. + signedMsg, err := signed.Sign(hdr, body, privateKey, associatedData...) + if err != nil { + panic(err) + } + + // Extract body without verification. Usually, you will not need this operation. + unverifiedBody, err := signed.ExtractUnverifiedBody(signedMsg) + if err != nil { + panic(err) + } + fmt.Println(string(unverifiedBody)) + // Output: + // very important message +} + +func ExampleVerify_basic() { + signedMsg, publicKey := basicSignedMessage() + + verifiedMsg, err := signed.Verify(signedMsg, publicKey) + if err != nil { + panic(err) + } + meta := verifiedMsg.Header.Metadata + keyID := verifiedMsg.Header.VerificationKeyID + body := verifiedMsg.Body + + fmt.Printf("meta: %q keyID: %q body: %q", meta, keyID, body) + // Output: + // meta: "metadata" keyID: "keyID" body: "very important message" +} + +func ExampleVerify_withAssociatedData() { + signedMsg, publicKey := signedMessageWithAssociatedData() + + _, err := signed.Verify(signedMsg, publicKey) + if err == nil { + panic("associated data is required") + } + + verifiedMsg, err := signed.Verify(signedMsg, publicKey, []byte("out-of-band")) + if err != nil { + panic(err) + } + meta := verifiedMsg.Header.Metadata + keyID := verifiedMsg.Header.VerificationKeyID + body := verifiedMsg.Body + + fmt.Printf("meta: %q keyID: %q body: %q", meta, keyID, body) + // Output: + // meta: "metadata" keyID: "keyID" body: "very important message" +} + +func basicSignedMessage() (*cryptopb.SignedMessage, crypto.PublicKey) { + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + panic(err) + } + hdr := signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA512, + Metadata: []byte("metadata"), + VerificationKeyID: []byte("keyID"), + } + body := []byte("very important message") + signedMsg, err := signed.Sign(hdr, body, privateKey) + if err != nil { + panic(err) + } + return signedMsg, privateKey.Public() +} + +func signedMessageWithAssociatedData() (*cryptopb.SignedMessage, crypto.PublicKey) { + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + panic(err) + } + hdr := signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA512, + Metadata: []byte("metadata"), + VerificationKeyID: []byte("keyID"), + AssociatedDataLength: 11, + } + body := []byte("very important message") + associatedData := []byte("out-of-band") + signedMsg, err := signed.Sign(hdr, body, privateKey, associatedData) + if err != nil { + panic(err) + } + return signedMsg, privateKey.Public() +} diff --git a/go/lib/scrypto/signed/export_test.go b/go/lib/scrypto/signed/export_test.go new file mode 100644 index 0000000000..f172d21e0c --- /dev/null +++ b/go/lib/scrypto/signed/export_test.go @@ -0,0 +1,40 @@ +// Copyright 2020 Anapaya Systems +// +// 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 signed + +import ( + "crypto" +) + +const ( + // PurePureEd25519 is used to check signature input creation is correct. + // Officially, we do not yet support ed25519. + PureEd25519 SignatureAlgorithm = 1 << 16 + pkEd25519 publicKeyAlgorithm = 1 << 16 +) + +func init() { + signatureAlgorithmDetails[PureEd25519] = struct { + name string + pubKeyAlgo publicKeyAlgorithm + hash crypto.Hash + }{ + name: "Ed25519", + pubKeyAlgo: pkEd25519, + hash: 0, + } +} + +var ComputeSignatureInput = computeSignatureInput diff --git a/go/lib/scrypto/signed/msg.go b/go/lib/scrypto/signed/msg.go index 692f62b79a..7f0524ccbe 100644 --- a/go/lib/scrypto/signed/msg.go +++ b/go/lib/scrypto/signed/msg.go @@ -42,6 +42,9 @@ type Header struct { Timestamp time.Time // Metadata is optional arbitrary data that is covered by the signature. Metadata []byte + // AssociatedDataLength is the length of associated data that is covered + // by the signature, but is not included in the header and body. + AssociatedDataLength int } // Message represents the signed message. @@ -50,11 +53,18 @@ type Message struct { Body []byte } -// Sign creates a signed message. -func Sign(hdr Header, body []byte, signer crypto.Signer) (*cryptopb.SignedMessage, error) { +// Sign creates a signed message. The associated data is not included in the +// header or body. +func Sign(hdr Header, body []byte, signer crypto.Signer, + associatedData ...[]byte) (*cryptopb.SignedMessage, error) { + if signer == nil { return nil, serrors.New("singer must not be nil") } + if l := associatedDataLen(associatedData...); l != hdr.AssociatedDataLength { + return nil, serrors.New("header specifies a different associated data length", + "expected", hdr.AssociatedDataLength, "actual", l) + } if err := checkPubKeyAlgo(hdr.SignatureAlgorithm, signer.Public()); err != nil { return nil, err } @@ -68,10 +78,11 @@ func Sign(hdr Header, body []byte, signer crypto.Signer) (*cryptopb.SignedMessag } inputHdr := &cryptopb.Header{ - SignatureAlgorithm: hdr.SignatureAlgorithm.toPB(), - Metadata: hdr.Metadata, - VerificationKeyId: hdr.VerificationKeyID, - Timestamp: ts, + SignatureAlgorithm: hdr.SignatureAlgorithm.toPB(), + Metadata: hdr.Metadata, + VerificationKeyId: hdr.VerificationKeyID, + Timestamp: ts, + AssociatedDataLength: int32(hdr.AssociatedDataLength), } rawHdr, err := proto.Marshal(inputHdr) if err != nil { @@ -85,8 +96,8 @@ func Sign(hdr Header, body []byte, signer crypto.Signer) (*cryptopb.SignedMessag if err != nil { return nil, serrors.WrapStr("packing signature input", err) } - input, hashAlgo := computeSignatureInput(hdr.SignatureAlgorithm, rawHdrAndBody) - signature, err := signer.Sign(rand.Reader, input, hashAlgo) + input, algo := computeSignatureInput(hdr.SignatureAlgorithm, rawHdrAndBody, associatedData...) + signature, err := signer.Sign(rand.Reader, input, algo) if err != nil { return nil, err } @@ -97,7 +108,9 @@ func Sign(hdr Header, body []byte, signer crypto.Signer) (*cryptopb.SignedMessag } // Verify verifies the signed message. -func Verify(signed *cryptopb.SignedMessage, key crypto.PublicKey) (*Message, error) { +func Verify(signed *cryptopb.SignedMessage, key crypto.PublicKey, + associatedData ...[]byte) (*Message, error) { + if key == nil { return nil, serrors.New("public key must not be nil") } @@ -105,11 +118,16 @@ func Verify(signed *cryptopb.SignedMessage, key crypto.PublicKey) (*Message, err if err != nil { return nil, serrors.WrapStr("extracting header", err) } + if l := associatedDataLen(associatedData...); l != hdr.AssociatedDataLength { + return nil, serrors.New("header specifies a different associated data length", + "expected", hdr.AssociatedDataLength, "actual", l) + } if err := checkPubKeyAlgo(hdr.SignatureAlgorithm, key); err != nil { return nil, err } - input, _ := computeSignatureInput(hdr.SignatureAlgorithm, signed.HeaderAndBody) + input, _ := computeSignatureInput(hdr.SignatureAlgorithm, signed.HeaderAndBody, + associatedData...) switch pub := key.(type) { case *ecdsa.PublicKey: @@ -139,16 +157,28 @@ func Verify(signed *cryptopb.SignedMessage, key crypto.PublicKey) (*Message, err }, nil } -func computeSignatureInput(algo SignatureAlgorithm, hdrAndBody []byte) ([]byte, crypto.Hash) { +func computeSignatureInput(algo SignatureAlgorithm, hdrAndBody []byte, + associatedData ...[]byte) ([]byte, crypto.Hash) { + hash := signatureAlgorithmDetails[algo].hash if hash == 0 { - return hdrAndBody, hash + input := make([]byte, len(hdrAndBody)+associatedDataLen(associatedData...)) + copy(input, hdrAndBody) + offset := len(hdrAndBody) + for _, d := range associatedData { + copy(input[offset:], d) + offset += len(d) + } + return input, hash } if !hash.Available() { panic(fmt.Sprintf("unavailable hash algorithm: %v", hash)) } h := hash.New() h.Write(hdrAndBody) + for _, d := range associatedData { + h.Write(d) + } return h.Sum(nil), hash } @@ -209,9 +239,18 @@ func extractHeaderAndBody(signed *cryptopb.SignedMessage) (*Header, []byte, erro } } return &Header{ - SignatureAlgorithm: signatureAlgorithmFromPB(hdr.SignatureAlgorithm), - VerificationKeyID: hdr.VerificationKeyId, - Timestamp: ts, - Metadata: hdr.Metadata, + SignatureAlgorithm: signatureAlgorithmFromPB(hdr.SignatureAlgorithm), + VerificationKeyID: hdr.VerificationKeyId, + Timestamp: ts, + Metadata: hdr.Metadata, + AssociatedDataLength: int(hdr.AssociatedDataLength), }, hdrAndBody.Body, nil } + +func associatedDataLen(associatedData ...[]byte) int { + var associatedDataLen int + for _, d := range associatedData { + associatedDataLen += len(d) + } + return associatedDataLen +} diff --git a/go/lib/scrypto/signed/msg_test.go b/go/lib/scrypto/signed/msg_test.go index 08e9fa376b..a7030fd03f 100644 --- a/go/lib/scrypto/signed/msg_test.go +++ b/go/lib/scrypto/signed/msg_test.go @@ -20,6 +20,8 @@ import ( "crypto/ed25519" "crypto/elliptic" "crypto/rand" + "crypto/sha256" + "crypto/sha512" "testing" "time" @@ -33,9 +35,10 @@ import ( func TestSign(t *testing.T) { testCases := map[string]struct { - Signer func(t *testing.T) crypto.Signer - Header signed.Header - ErrAssertion assert.ErrorAssertionFunc + Signer func(t *testing.T) crypto.Signer + Header signed.Header + AssociatedData []byte + ErrAssertion assert.ErrorAssertionFunc }{ "ECDSAWithSHA256": { Signer: func(t *testing.T) crypto.Signer { @@ -79,6 +82,22 @@ func TestSign(t *testing.T) { }, ErrAssertion: assert.NoError, }, + "with associated data": { + Signer: func(t *testing.T) crypto.Signer { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + return priv + }, + Header: signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA256, + Metadata: []byte("some metadata"), + Timestamp: time.Now().UTC(), + VerificationKeyID: []byte("some key id"), + AssociatedDataLength: 12, + }, + AssociatedData: []byte("not included"), + ErrAssertion: assert.NoError, + }, "no timestamp": { Signer: func(t *testing.T) crypto.Signer { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) @@ -158,6 +177,22 @@ func TestSign(t *testing.T) { }, ErrAssertion: assert.Error, }, + "associated data length does not match": { + Signer: func(t *testing.T) crypto.Signer { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + return priv + }, + Header: signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA256, + Metadata: []byte("some metadata"), + Timestamp: time.Now().UTC(), + VerificationKeyID: []byte("some key id"), + AssociatedDataLength: 10, + }, + AssociatedData: []byte("not included"), + ErrAssertion: assert.Error, + }, } for name, tc := range testCases { name, tc := name, tc @@ -165,7 +200,7 @@ func TestSign(t *testing.T) { t.Parallel() body := []byte("super securely signed message") - msg, err := signed.Sign(tc.Header, body, tc.Signer(t)) + msg, err := signed.Sign(tc.Header, body, tc.Signer(t), tc.AssociatedData) tc.ErrAssertion(t, err) if err != nil { return @@ -182,12 +217,13 @@ func TestVerify(t *testing.T) { now := time.Now().UTC() testCases := map[string]struct { - Input func(t *testing.T) (*cryptopb.SignedMessage, crypto.PublicKey) - Message *signed.Message - ErrAssertion assert.ErrorAssertionFunc + Input func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) + Message *signed.Message + AssociatedData []byte + ErrAssertion assert.ErrorAssertionFunc }{ "ECDSAWithSHA256": { - Input: func(t *testing.T) (*cryptopb.SignedMessage, crypto.PublicKey) { + Input: func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) hdr := signed.Header{ @@ -199,7 +235,7 @@ func TestVerify(t *testing.T) { s, err := signed.Sign(hdr, []byte("some body"), priv) require.NoError(t, err) - return s, priv.Public() + return s, nil, priv.Public() }, Message: &signed.Message{ Header: signed.Header{ @@ -213,7 +249,7 @@ func TestVerify(t *testing.T) { ErrAssertion: assert.NoError, }, "ECDSAWithSHA384": { - Input: func(t *testing.T) (*cryptopb.SignedMessage, crypto.PublicKey) { + Input: func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) hdr := signed.Header{ @@ -225,7 +261,7 @@ func TestVerify(t *testing.T) { s, err := signed.Sign(hdr, []byte("some body"), priv) require.NoError(t, err) - return s, priv.Public() + return s, nil, priv.Public() }, Message: &signed.Message{ Header: signed.Header{ @@ -239,7 +275,7 @@ func TestVerify(t *testing.T) { ErrAssertion: assert.NoError, }, "ECDSAWithSHA512": { - Input: func(t *testing.T) (*cryptopb.SignedMessage, crypto.PublicKey) { + Input: func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) hdr := signed.Header{ @@ -251,7 +287,7 @@ func TestVerify(t *testing.T) { s, err := signed.Sign(hdr, []byte("some body"), priv) require.NoError(t, err) - return s, priv.Public() + return s, nil, priv.Public() }, Message: &signed.Message{ Header: signed.Header{ @@ -264,8 +300,38 @@ func TestVerify(t *testing.T) { }, ErrAssertion: assert.NoError, }, + "with associated data": { + Input: func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + hdr := signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA256, + Metadata: []byte("some metadata"), + Timestamp: now, + VerificationKeyID: []byte("some key id"), + AssociatedDataLength: 12, + } + + data := []byte("not included") + + s, err := signed.Sign(hdr, []byte("some body"), priv, data) + require.NoError(t, err) + return s, data, priv.Public() + }, + Message: &signed.Message{ + Header: signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA256, + Metadata: []byte("some metadata"), + Timestamp: now, + VerificationKeyID: []byte("some key id"), + AssociatedDataLength: 12, + }, + Body: []byte("some body"), + }, + ErrAssertion: assert.NoError, + }, "nil key": { - Input: func(t *testing.T) (*cryptopb.SignedMessage, crypto.PublicKey) { + Input: func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) hdr := signed.Header{ @@ -277,22 +343,22 @@ func TestVerify(t *testing.T) { s, err := signed.Sign(hdr, []byte("some body"), priv) require.NoError(t, err) - return s, nil + return s, nil, nil }, ErrAssertion: assert.Error, }, "malformed headerAndBody": { - Input: func(t *testing.T) (*cryptopb.SignedMessage, crypto.PublicKey) { + Input: func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) return &cryptopb.SignedMessage{ HeaderAndBody: []byte("someweirdmalformedthingy"), - }, priv + }, nil, priv }, ErrAssertion: assert.Error, }, "malformed header": { - Input: func(t *testing.T) (*cryptopb.SignedMessage, crypto.PublicKey) { + Input: func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) @@ -304,7 +370,26 @@ func TestVerify(t *testing.T) { require.NoError(t, err) return &cryptopb.SignedMessage{ HeaderAndBody: rawHdrAndBody, - }, priv + }, nil, priv + }, + ErrAssertion: assert.Error, + }, + "associated data missing": { + Input: func(t *testing.T) (*cryptopb.SignedMessage, []byte, crypto.PublicKey) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + hdr := signed.Header{ + SignatureAlgorithm: signed.ECDSAWithSHA256, + Metadata: []byte("some metadata"), + Timestamp: now, + VerificationKeyID: []byte("some key id"), + AssociatedDataLength: 12, + } + + data := []byte("not included") + s, err := signed.Sign(hdr, []byte("some body"), priv, data) + require.NoError(t, err) + return s, nil, priv.Public() }, ErrAssertion: assert.Error, }, @@ -314,11 +399,74 @@ func TestVerify(t *testing.T) { name, tc := name, tc t.Run(name, func(t *testing.T) { t.Parallel() - signedMsg, key := tc.Input(t) - msg, err := signed.Verify(signedMsg, key) + signedMsg, associatedData, key := tc.Input(t) + msg, err := signed.Verify(signedMsg, key, associatedData) tc.ErrAssertion(t, err) assert.Equal(t, tc.Message, msg) }) } +} +func TestComputeSignatureInput(t *testing.T) { + testCases := map[string]struct { + Algo signed.SignatureAlgorithm + HeaderAndBody []byte + AssociatedData [][]byte + Expected []byte + }{ + "PureEd25519": { + Algo: signed.PureEd25519, + HeaderAndBody: []byte("headerAndBody"), + AssociatedData: [][]byte{ + []byte("Associated"), + []byte("Data"), + }, + Expected: []byte("headerAndBodyAssociatedData"), + }, + "ECDSAWithSHA256": { + Algo: signed.ECDSAWithSHA256, + HeaderAndBody: []byte("headerAndBody"), + AssociatedData: [][]byte{ + []byte("Associated"), + []byte("Data"), + }, + Expected: func() []byte { + sum := sha256.Sum256([]byte("headerAndBodyAssociatedData")) + return sum[:] + }(), + }, + "ECDSAWithSHA384": { + Algo: signed.ECDSAWithSHA384, + HeaderAndBody: []byte("headerAndBody"), + AssociatedData: [][]byte{ + []byte("Associated"), + []byte("Data"), + }, + Expected: func() []byte { + sum := sha512.Sum384([]byte("headerAndBodyAssociatedData")) + return sum[:] + }(), + }, + "ECDSAWithSHA512": { + Algo: signed.ECDSAWithSHA512, + HeaderAndBody: []byte("headerAndBody"), + AssociatedData: [][]byte{ + []byte("Associated"), + []byte("Data"), + }, + Expected: func() []byte { + sum := sha512.Sum512([]byte("headerAndBodyAssociatedData")) + return sum[:] + }(), + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + in, _ := signed.ComputeSignatureInput(tc.Algo, tc.HeaderAndBody, tc.AssociatedData...) + assert.Equal(t, tc.Expected, in) + }) + } } diff --git a/go/pkg/proto/crypto/signed.pb.go b/go/pkg/proto/crypto/signed.pb.go index 9e7b74d848..8d2f3337f1 100644 --- a/go/pkg/proto/crypto/signed.pb.go +++ b/go/pkg/proto/crypto/signed.pb.go @@ -138,10 +138,11 @@ type Header struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SignatureAlgorithm SignatureAlgorithm `protobuf:"varint,1,opt,name=signature_algorithm,json=signatureAlgorithm,proto3,enum=proto.crypto.v1.SignatureAlgorithm" json:"signature_algorithm,omitempty"` - VerificationKeyId []byte `protobuf:"bytes,2,opt,name=verification_key_id,json=verificationKeyId,proto3" json:"verification_key_id,omitempty"` - Timestamp *timestamp.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - Metadata []byte `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` + SignatureAlgorithm SignatureAlgorithm `protobuf:"varint,1,opt,name=signature_algorithm,json=signatureAlgorithm,proto3,enum=proto.crypto.v1.SignatureAlgorithm" json:"signature_algorithm,omitempty"` + VerificationKeyId []byte `protobuf:"bytes,2,opt,name=verification_key_id,json=verificationKeyId,proto3" json:"verification_key_id,omitempty"` + Timestamp *timestamp.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Metadata []byte `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` + AssociatedDataLength int32 `protobuf:"varint,5,opt,name=associated_data_length,json=associatedDataLength,proto3" json:"associated_data_length,omitempty"` } func (x *Header) Reset() { @@ -204,6 +205,13 @@ func (x *Header) GetMetadata() []byte { return nil } +func (x *Header) GetAssociatedDataLength() int32 { + if x != nil { + return x.AssociatedDataLength + } + return 0 +} + type HeaderAndBodyInternal struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -272,7 +280,7 @@ var file_proto_crypto_v1_signed_proto_rawDesc = []byte{ 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x6e, 0x64, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xe4, 0x01, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x9a, 0x02, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x54, 0x0a, 0x13, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x76, @@ -286,27 +294,31 @@ var file_proto_crypto_v1_signed_proto_rawDesc = []byte{ 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x43, - 0x0a, 0x15, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x6e, 0x64, 0x42, 0x6f, 0x64, 0x79, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, - 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, - 0x6f, 0x64, 0x79, 0x2a, 0xba, 0x01, 0x0a, 0x12, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x23, 0x0a, 0x1f, 0x53, 0x49, - 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, - 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x29, 0x0a, 0x25, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4c, 0x47, - 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x45, 0x43, 0x44, 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, - 0x48, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x01, 0x12, 0x29, 0x0a, 0x25, 0x53, 0x49, - 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, - 0x4d, 0x5f, 0x45, 0x43, 0x44, 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x53, 0x48, 0x41, - 0x33, 0x38, 0x34, 0x10, 0x02, 0x12, 0x29, 0x0a, 0x25, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x34, + 0x0a, 0x16, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, + 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x4c, 0x65, + 0x6e, 0x67, 0x74, 0x68, 0x22, 0x43, 0x0a, 0x15, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x6e, + 0x64, 0x42, 0x6f, 0x64, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x16, 0x0a, + 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x2a, 0xba, 0x01, 0x0a, 0x12, 0x53, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, + 0x12, 0x23, 0x0a, 0x1f, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4c, + 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x29, 0x0a, 0x25, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x45, 0x43, 0x44, - 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0x03, - 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, - 0x63, 0x69, 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, - 0x67, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x01, + 0x12, 0x29, 0x0a, 0x25, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4c, + 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x45, 0x43, 0x44, 0x53, 0x41, 0x5f, 0x57, 0x49, + 0x54, 0x48, 0x5f, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x10, 0x02, 0x12, 0x29, 0x0a, 0x25, 0x53, + 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, + 0x48, 0x4d, 0x5f, 0x45, 0x43, 0x44, 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x53, 0x48, + 0x41, 0x35, 0x31, 0x32, 0x10, 0x03, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/proto/crypto/v1/signed.proto b/proto/crypto/v1/signed.proto index 306772fab5..dc22b524d9 100644 --- a/proto/crypto/v1/signed.proto +++ b/proto/crypto/v1/signed.proto @@ -34,7 +34,8 @@ enum SignatureAlgorithm { message SignedMessage { // Encoded header and body. bytes header_and_body = 1; - // Raw signature. + // Raw signature. The signature is computed over the concatenation of the + // header and body, and the optional associated data. bytes signature = 2; } @@ -47,6 +48,10 @@ message Header { google.protobuf.Timestamp timestamp = 3; // Optional arbitrary per-protocol metadata. bytes metadata = 4; + // Length of associated data that is covered by the signature, but is not + // included in the header and body. This is zero, if no associated data is + // covered by the signature. + int32 associated_data_length = 5; } // Low-level representation of HeaderAndBody used for signature computation