From 9a629bb5454ba616166d9ed0367f71256485d5a4 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Tue, 7 Nov 2017 21:39:08 -0500 Subject: [PATCH] validate SECP256k1 signatures --- build.sh | 1 + claim/claim.go | 41 +++++++++--- claim/decode_test.go | 12 +--- claim/pretty.go | 12 +++- claim/serialization.go | 39 +++++++++--- claim/test_verify_signature.go | 31 --------- claim/validator.go | 112 +++++++++++++++++++++++++-------- claim/validator_test.go | 43 +++++++++++++ test.sh | 3 +- 9 files changed, 205 insertions(+), 89 deletions(-) delete mode 100644 claim/test_verify_signature.go create mode 100644 claim/validator_test.go diff --git a/build.sh b/build.sh index a5a8dd2..43d669a 100755 --- a/build.sh +++ b/build.sh @@ -4,3 +4,4 @@ set -euxo pipefail echo "Building protobuf files" rm -rf pb/*.pb.go protoc --go_out=. pb/*.proto +go build ./... \ No newline at end of file diff --git a/claim/claim.go b/claim/claim.go index 69d7c3f..228a275 100644 --- a/claim/claim.go +++ b/claim/claim.go @@ -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 +} diff --git a/claim/decode_test.go b/claim/decode_test.go index 5bb1cfd..8ea2a0a 100644 --- a/claim/decode_test.go +++ b/claim/decode_test.go @@ -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") } } diff --git a/claim/pretty.go b/claim/pretty.go index 0932c3a..295651e 100644 --- a/claim/pretty.go +++ b/claim/pretty.go @@ -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 \ No newline at end of file diff --git a/claim/serialization.go b/claim/serialization.go index b20caac..b1058f0 100644 --- a/claim/serialization.go +++ b/claim/serialization.go @@ -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) diff --git a/claim/test_verify_signature.go b/claim/test_verify_signature.go deleted file mode 100644 index 80bf879..0000000 --- a/claim/test_verify_signature.go +++ /dev/null @@ -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 diff --git a/claim/validator.go b/claim/validator.go index bca76f7..b7fe45a 100644 --- a/claim/validator.go +++ b/claim/validator.go @@ -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) } diff --git a/claim/validator_test.go b/claim/validator_test.go new file mode 100644 index 0000000..15d68b6 --- /dev/null +++ b/claim/validator_test.go @@ -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) + } +} diff --git a/test.sh b/test.sh index e147e46..8091974 100755 --- a/test.sh +++ b/test.sh @@ -1,2 +1 @@ -go build -go test ./... +go test ./... -v