Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signed: add associated data #3870

Merged
merged 1 commit into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion go/lib/scrypto/signed/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
163 changes: 163 additions & 0 deletions go/lib/scrypto/signed/example_test.go
Original file line number Diff line number Diff line change
@@ -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()
}
40 changes: 40 additions & 0 deletions go/lib/scrypto/signed/export_test.go
Original file line number Diff line number Diff line change
@@ -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
71 changes: 55 additions & 16 deletions go/lib/scrypto/signed/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -97,19 +108,26 @@ 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")
}
hdr, body, err := extractHeaderAndBody(signed)
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:
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
Loading