validate SECP256k1 signatures

This commit is contained in:
Jack Robison 2017-11-07 21:39:08 -05:00
parent c62c175d8b
commit 9a629bb545
No known key found for this signature in database
GPG key ID: 284699E7404E3CFF
9 changed files with 205 additions and 89 deletions

View file

@ -4,3 +4,4 @@ set -euxo pipefail
echo "Building protobuf files"
rm -rf pb/*.pb.go
protoc --go_out=. pb/*.proto
go build ./...

View file

@ -7,27 +7,29 @@ import (
"errors"
)
type Claim struct {
protobuf pb.Claim
type ClaimHelper struct {
*pb.Claim
}
func (claim *Claim) LoadFromBytes(raw_claim []byte) (error) {
if claim.protobuf.String() != "" {
func (claim *ClaimHelper) LoadFromBytes(raw_claim []byte) (error) {
if claim.String() != "" {
return errors.New("already initialized")
}
if len(raw_claim) < 1 {
return errors.New("there is nothing to decode")
}
claim_pb := &pb.Claim{}
err := proto.Unmarshal(raw_claim, claim_pb)
if err != nil {
return err
}
claim.protobuf = *claim_pb
*claim = ClaimHelper{claim_pb}
return nil
}
func (claim *Claim) LoadFromHexString(claim_hex string) (error) {
func (claim *ClaimHelper) LoadFromHexString(claim_hex string) (error) {
buf, err := hex.DecodeString(claim_hex)
if err != nil {
return err
@ -35,8 +37,8 @@ func (claim *Claim) LoadFromHexString(claim_hex string) (error) {
return claim.LoadFromBytes(buf)
}
func DecodeClaimBytes(serialized []byte) (*Claim, error) {
claim := &Claim{}
func DecodeClaimBytes(serialized []byte) (*ClaimHelper, error) {
claim := &ClaimHelper{&pb.Claim{}}
err := claim.LoadFromBytes(serialized)
if err != nil {
return nil, err
@ -44,10 +46,31 @@ func DecodeClaimBytes(serialized []byte) (*Claim, error) {
return claim, nil
}
func DecodeClaimHex(serialized string) (*Claim, error) {
func DecodeClaimHex(serialized string) (*ClaimHelper, error) {
claim_bytes, err := hex.DecodeString(serialized)
if err != nil {
return nil, err
}
return DecodeClaimBytes(claim_bytes)
}
func (m *ClaimHelper) GetStream() *pb.Stream {
if m != nil {
return m.Stream
}
return nil
}
func (m *ClaimHelper) GetCertificate() *pb.Certificate {
if m != nil {
return m.Certificate
}
return nil
}
func (m *ClaimHelper) GetPublisherSignature() *pb.Signature {
if m != nil {
return m.PublisherSignature
}
return nil
}

View file

@ -14,15 +14,10 @@ var raw_claims = []string{
func TestDecodeClaims(t *testing.T) {
for _, claim_hex := range(raw_claims) {
claim := Claim{}
err := claim.LoadFromHexString(claim_hex)
claim, err := DecodeClaimHex(claim_hex)
if err != nil {
t.Error(err)
}
err = claim.LoadFromHexString(claim_hex)
if err.Error() != "already initialized" {
t.Error(err)
}
serialized_hex, err := claim.SerializedHexString()
if err != nil {
t.Error(err)
@ -35,8 +30,7 @@ func TestDecodeClaims(t *testing.T) {
func TestStripSignature(t *testing.T) {
claim_hex := raw_claims[1]
claim := Claim{}
err := claim.LoadFromHexString(claim_hex)
claim, err := DecodeClaimHex(claim_hex)
if err != nil {
t.Error(err)
}
@ -45,6 +39,6 @@ func TestStripSignature(t *testing.T) {
t.Error(err)
}
if hex.EncodeToString(no_sig) != raw_claims[2] {
t.Error("Failed to remove signature")
t.Error("failed to remove signature")
}
}

View file

@ -4,7 +4,13 @@ import (
"github.com/golang/protobuf/jsonpb"
)
func (claim *Claim) RenderJSON() (string, error) {
marshaler := jsonpb.Marshaler{}
return marshaler.MarshalToString(&claim.protobuf)
func marshalToString(claim *ClaimHelper) (string, error) {
m_pb := &jsonpb.Marshaler{}
return m_pb.MarshalToString(claim)
}
func (claim *ClaimHelper) RenderJSON() (string, error) {
return marshalToString(claim)
}
//TODO: encode byte arrays with b58 for addresses and b16 for source hashes instead of the default of b64

View file

@ -1,21 +1,42 @@
package claim
import (
"../pb"
"errors"
"github.com/golang/protobuf/proto"
"encoding/hex"
"../pb"
)
func (claim *Claim) Serialized() ([]byte, error) {
serialized := claim.protobuf.String()
func (claim *ClaimHelper) Serialized() ([]byte, error) {
serialized := claim.String()
if serialized == "" {
return nil, errors.New("not initialized")
}
return proto.Marshal(&claim.protobuf)
v := claim.GetVersion()
t := claim.GetClaimType()
return proto.Marshal(
&pb.Claim{
Version: &v,
ClaimType: &t,
Stream: claim.GetStream(),
Certificate: claim.GetCertificate(),
PublisherSignature: claim.GetPublisherSignature()})
}
func (claim *Claim) SerializedHexString() (string, error) {
func (claim *ClaimHelper) GetProtobuf() (*pb.Claim) {
v := claim.GetVersion()
t := claim.GetClaimType()
return &pb.Claim{
Version: &v,
ClaimType: &t,
Stream: claim.GetStream(),
Certificate: claim.GetCertificate(),
PublisherSignature: claim.GetPublisherSignature()}
}
func (claim *ClaimHelper) SerializedHexString() (string, error) {
serialized, err := claim.Serialized()
if err != nil {
return "", err
@ -24,11 +45,11 @@ func (claim *Claim) SerializedHexString() (string, error) {
return serialized_hex, nil
}
func (claim *Claim) SerializedNoSignature() ([]byte, error) {
if claim.protobuf.String() == "" {
func (claim *ClaimHelper) SerializedNoSignature() ([]byte, error) {
if claim.String() == "" {
return nil, errors.New("not initialized")
}
if claim.protobuf.GetPublisherSignature() == nil {
if claim.GetPublisherSignature() == nil {
serialized, err := claim.Serialized()
if err != nil {
return nil, err
@ -36,7 +57,7 @@ func (claim *Claim) SerializedNoSignature() ([]byte, error) {
return serialized, nil
} else {
clone := &pb.Claim{}
proto.Merge(clone, &claim.protobuf)
proto.Merge(clone, claim.GetProtobuf())
proto.ClearAllExtensions(clone.PublisherSignature)
clone.PublisherSignature = nil
return proto.Marshal(clone)

View file

@ -1,31 +0,0 @@
package claim
import "testing"
import (
"../pb"
"encoding/hex"
"github.com/golang/protobuf/proto"
)
func TestValidateClaimSignature(t *testing.T) {
cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367"
signed_claim_hex := "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65"
signed_claim := &pb.Claim{}
cert_claim := &pb.Claim{}
buf, _ := hex.DecodeString(signed_claim_hex)
proto.Unmarshal(buf, signed_claim)
buf, _ = hex.DecodeString(cert_claim_hex)
proto.Unmarshal(buf, cert_claim)
//signed_claim := DecodeAddress("bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt")
//cert_claim_id, _ := hex.DecodeString("251305ca93d4dbedb50dceb282ebcb7b07b7ac65")
}
// 251305ca93d4dbedb50dceb282ebcb7b07b7ac65
// bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt

View file

@ -4,39 +4,99 @@ import (
"../address"
"crypto/sha256"
"errors"
"fmt"
"encoding/asn1"
"crypto/x509/pkix"
"github.com/btcsuite/btcd/btcec"
"math/big"
"crypto/ecdsa"
"encoding/hex"
)
func GetClaimSignatureDigest(claim_address [25]byte, certificate_id [20]byte, serialized_no_sig []byte) [32]byte {
type publicKeyInfo struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
const SECP256k1 = "SECP256k1"
func GetClaimSignatureDigest(claimAddress [25]byte, certificateId [20]byte, serializedNoSig []byte) [32]byte {
combined := []byte{}
for _, c := range claim_address {combined = append(combined, c)}
for _, c := range serialized_no_sig {combined = append(combined, c)}
for _, c := range certificate_id {combined = append(combined, c)}
for _, c := range claimAddress {combined = append(combined, c)}
for _, c := range serializedNoSig {combined = append(combined, c)}
for _, c := range certificateId {combined = append(combined, c)}
digest := sha256.Sum256(combined)
return [32]byte(digest)
}
func VerifyDigest(signature [64]byte, digest [32]byte) bool {
/*
def verify_digest(self, signature, digest, sigdecode=sigdecode_string):
if len(digest) > self.curve.baselen:
raise BadDigestError("this curve (%s) is too short "
"for your digest (%d)" % (self.curve.name,
8*len(digest)))
number = string_to_number(digest)
r, s = sigdecode(signature, self.pubkey.order)
sig = ecdsa.Signature(r, s)
if self.pubkey.verifies(number, sig):
return True
raise BadSignatureError
*/
return true
func (claim *ClaimHelper) GetCertificatePublicKey() (btcec.PublicKey, error) {
derBytes := claim.GetCertificate().GetPublicKey()
pub := publicKeyInfo{}
asn1.Unmarshal(derBytes, &pub)
pubkey_bytes := []byte(pub.PublicKey.Bytes)
p, err := btcec.ParsePubKey(pubkey_bytes, btcec.S256())
if err != nil {
fmt.Println("parse public key error: ", err)
}
return *p, err
}
func (claim *Claim) ValidateClaimSignature(claim_address [25]byte, certificate_id [20]byte, signature [64]byte) (bool, error) {
claim_address, err := address.ValidateAddress(claim_address)
if err != nil {return false, errors.New("invalid address")}
serialized_no_sig, err := claim.SerializedNoSignature()
if err != nil {return false, errors.New("serialization error")}
claim_digest := GetClaimSignatureDigest(claim_address, certificate_id, serialized_no_sig)
return VerifyDigest(signature, claim_digest), nil
func (claim *ClaimHelper) VerifyDigest(certificate *ClaimHelper, signature [64]byte, digest [32]byte) bool {
public_key, err := certificate.GetCertificatePublicKey()
if err != nil {
fmt.Println("parse public key error: ", err)
return false
}
if claim.PublisherSignature.SignatureType.String() == SECP256k1 {
R := &big.Int{}
S := &big.Int{}
R.SetBytes(signature[0:32])
S.SetBytes(signature[32:64])
return ecdsa.Verify(public_key.ToECDSA(), digest[:], R, S)
}
fmt.Println("unknown curve:", claim.PublisherSignature.SignatureType.String())
return false
}
func (claim *ClaimHelper) ValidateClaimSignatureBytes(certificate *ClaimHelper, claimAddress [25]byte, certificateId [20]byte) (bool, error) {
signature := claim.GetPublisherSignature()
if signature == nil {
return false, errors.New("claim does not have a signature")
}
signatureSlice := signature.GetSignature()
signatureBytes := [64]byte{}
for i := range signatureBytes {
signatureBytes[i] = signatureSlice[i]
}
claimAddress, err := address.ValidateAddress(claimAddress)
if err != nil {
return false, errors.New("invalid address")
}
serializedNoSig, err := claim.SerializedNoSignature()
if err != nil {
return false, errors.New("serialization error")
}
claimDigest := GetClaimSignatureDigest(claimAddress, certificateId, serializedNoSig)
return claim.VerifyDigest(certificate, signatureBytes, claimDigest), nil
}
func (claim *ClaimHelper) ValidateClaimSignature(certificate *ClaimHelper, claimAddress string, certificateId string) (bool, error) {
addressBytes, err := address.DecodeAddress(claimAddress)
if err != nil {
return false, err
}
certificateIdSlice, err := hex.DecodeString(certificateId)
if err != nil {
return false, err
}
certificateIdBytes := [20]byte{}
for i := range certificateIdBytes {
certificateIdBytes[i] = certificateIdSlice[i]
}
return claim.ValidateClaimSignatureBytes(certificate, addressBytes, certificateIdBytes)
}

43
claim/validator_test.go Normal file
View file

@ -0,0 +1,43 @@
package claim
import (
"testing"
)
func TestValidateClaimSignature(t *testing.T) {
cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367"
signed_claim_hex := "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65"
signed_claim, _ := DecodeClaimHex(signed_claim_hex)
cert_claim, _ := DecodeClaimHex(cert_claim_hex)
claim_addr := "bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt"
cert_id := "251305ca93d4dbedb50dceb282ebcb7b07b7ac65"
result, err := signed_claim.ValidateClaimSignature(cert_claim, claim_addr, cert_id)
if err != nil {
t.Error(err)
}
if result != true {
t.Error("failed to validate signature:", result)
}
}
func TestFailToValidateClaimSignature(t *testing.T) {
cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367"
signed_claim_hex := "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65"
signed_claim, _ := DecodeClaimHex(signed_claim_hex)
cert_claim, _ := DecodeClaimHex(cert_claim_hex)
claim_addr := "bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt"
cert_id := "251305ca93d4dbedb50dceb282ebcb7b07b7ac64"
result, err := signed_claim.ValidateClaimSignature(cert_claim, claim_addr, cert_id)
if err != nil {
t.Error(err)
}
if result != false {
t.Error("failed to validate signature:", result)
}
}

View file

@ -1,2 +1 @@
go build
go test ./...
go test ./... -v