From 3641ff06b56ff0afac43e861862119b536884b22 Mon Sep 17 00:00:00 2001 From: Bohdan Kuchma Date: Tue, 10 Dec 2024 17:00:55 +0200 Subject: [PATCH] Unmarshal signature as ASN.1 object. Add hash unique constraint --- .../algorithms/{brainpool.go => ecdsa.go} | 6 +- internal/assets/migrations/001_initial.sql | 5 +- internal/data/document_sod.go | 3 +- internal/data/postgres/document_sod.go | 4 +- internal/service/api/handlers/register.go | 63 ++++++++++++++----- internal/types/signature_algorithm.go | 26 +------- 6 files changed, 59 insertions(+), 48 deletions(-) rename internal/algorithms/{brainpool.go => ecdsa.go} (65%) diff --git a/internal/algorithms/brainpool.go b/internal/algorithms/ecdsa.go similarity index 65% rename from internal/algorithms/brainpool.go rename to internal/algorithms/ecdsa.go index 7914a68..bba11ae 100644 --- a/internal/algorithms/brainpool.go +++ b/internal/algorithms/ecdsa.go @@ -5,17 +5,19 @@ import ( "crypto/sha256" "encoding/asn1" "math/big" + + "gitlab.com/distributed_lab/logan/v3/errors" ) type ECDSASignature struct { R, S *big.Int } -func VerifyBrainpool(data, sig []byte, publicKey *ecdsa.PublicKey) error { +func VerifyECDSA(data, sig []byte, publicKey *ecdsa.PublicKey) error { var esig ECDSASignature _, err := asn1.Unmarshal(sig, &esig) if err != nil { - return err + return errors.New("failed to unmarshal ASN.1 ECDSA signature") } hash := sha256.Sum256(data) if ok := ecdsa.Verify(publicKey, hash[:], esig.R, esig.S); !ok { diff --git a/internal/assets/migrations/001_initial.sql b/internal/assets/migrations/001_initial.sql index 020750c..a887be7 100644 --- a/internal/assets/migrations/001_initial.sql +++ b/internal/assets/migrations/001_initial.sql @@ -15,12 +15,13 @@ create table document_sod pem_file varchar(4096) not null, error_kind smallint, -- 0 - signed attributes validation failed, 1 - PEM file parsing failed, 2 - PEM file validation failed, 3 - signature verification failed error varchar(1024), -- error message - unique nulls not distinct (hash_algorithm, signature_algorithm, signed_attributes, encapsulated_content, signature, - aa_signature, error_kind, error) + hash char(64) not null, -- SHA256 hash used for unique constraint {hash_algorithm, signature_algorithm, signed_attributes, encapsulated_content, signature, error_kind, error} + unique (hash) -- We need to ensure that we won't store the same document with the same error multiple times. -- Perhaps the same document can fail verification with different errors ); + -- +migrate Down drop table document_sod; diff --git a/internal/data/document_sod.go b/internal/data/document_sod.go index a0ac77e..ead1f17 100644 --- a/internal/data/document_sod.go +++ b/internal/data/document_sod.go @@ -20,11 +20,12 @@ type DocumentSOD struct { DG15 string `db:"dg15" structs:"dg15"` HashAlgorigthm types.HashAlgorithm `db:"hash_algorithm" structs:"hash_algorithm"` SignatureAlgorithm types.SignatureAlgorithm `db:"signature_algorithm" structs:"signature_algorithm"` - SignedAttributed string `db:"signed_attributes" structs:"signed_attributes"` + SignedAttributes string `db:"signed_attributes" structs:"signed_attributes"` EncapsulatedContent string `db:"encapsulated_content" structs:"encapsulated_content"` Signature string `db:"signature" structs:"signature"` AaSignature string `db:"aa_signature" structs:"aa_signature"` PemFile string `db:"pem_file" structs:"pem_file"` + Hash string `db:"hash" structs:"hash"` ErrorKind *types.DocumentSODErrorKind `db:"error_kind" structs:"error_kind"` Error *string `db:"error" structs:"error"` } diff --git a/internal/data/postgres/document_sod.go b/internal/data/postgres/document_sod.go index a40111b..58a735f 100644 --- a/internal/data/postgres/document_sod.go +++ b/internal/data/postgres/document_sod.go @@ -64,9 +64,7 @@ func (q *DocumentSODQ) Insert(value data.DocumentSOD) (*data.DocumentSOD, error) var result data.DocumentSOD clauses := structs.Map(value) stmt := sq.Insert(documentSODTableName).SetMap(clauses).Suffix( - "on conflict " + - "(hash_algorithm, signature_algorithm, signed_attributes, encapsulated_content, signature, aa_signature, error_kind, error) " + - "do update set updated_at = current_timestamp returning *", + "on conflict (hash) do update set updated_at = current_timestamp returning *", ) err := q.db.Get(&result, stmt) if errors.Is(err, sql.ErrNoRows) { diff --git a/internal/service/api/handlers/register.go b/internal/service/api/handlers/register.go index 8bc1828..2322dfe 100644 --- a/internal/service/api/handlers/register.go +++ b/internal/service/api/handlers/register.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/ecdsa" "crypto/rand" + "crypto/sha256" "encoding/asn1" "encoding/hex" "encoding/json" @@ -49,19 +50,35 @@ func Register(w http.ResponseWriter, r *http.Request) { DG15: req.Data.Attributes.DocumentSod.Dg15, HashAlgorigthm: algorithmPair.HashAlgorithm, SignatureAlgorithm: algorithmPair.SignatureAlgorithm, - SignedAttributed: req.Data.Attributes.DocumentSod.SignedAttributes, + SignedAttributes: req.Data.Attributes.DocumentSod.SignedAttributes, EncapsulatedContent: req.Data.Attributes.DocumentSod.EncapsulatedContent, Signature: req.Data.Attributes.DocumentSod.Signature, - - PemFile: req.Data.Attributes.DocumentSod.PemFile, - ErrorKind: nil, - Error: nil, + AaSignature: req.Data.Attributes.DocumentSod.AaSignature, + PemFile: req.Data.Attributes.DocumentSod.PemFile, + ErrorKind: nil, + Error: nil, } var response *resources.SignatureResponse var jsonError []*jsonapi.ErrorObject defer func() { + // SHA256 hash used for unique constraint reserved for expansion, since postgresql has index limit + resultHash := sha256.New() + + message := fmt.Sprintf( + "%s%s%s%s%s", + documentSOD.HashAlgorigthm, documentSOD.SignatureAlgorithm, documentSOD.SignedAttributes, + documentSOD.EncapsulatedContent, documentSOD.Signature, + ) + + if documentSOD.Error != nil { + message += fmt.Sprintf("%s%s", documentSOD.ErrorKind, *documentSOD.Error) + } + + resultHash.Write([]byte(message)) + documentSOD.Hash = hex.EncodeToString(resultHash.Sum(nil)) + if _, err := api.DocumentSODQ(r).Insert(documentSOD); err != nil { api.Log(r).WithError(err).Error("failed to insert document SOD") ape.RenderErr(w, problems.InternalError()) @@ -129,7 +146,7 @@ func Register(w http.ResponseWriter, r *http.Request) { return } - slaveSignature, err := hex.DecodeString(req.Data.Attributes.DocumentSod.Signature) + slaveSignatureHex, err := hex.DecodeString(req.Data.Attributes.DocumentSod.Signature) if err != nil { log.WithError(err).Error("failed to decode slaveSignature") jsonError = problems.BadRequest(validation.Errors{ @@ -138,6 +155,15 @@ func Register(w http.ResponseWriter, r *http.Request) { return } + var slaveSignature asn1.RawValue + if _, err := asn1.Unmarshal(slaveSignatureHex, &slaveSignature); err != nil { + log.WithError(err).Error("failed to unmarshal slaveSignature") + jsonError = problems.BadRequest(validation.Errors{ + "slaveSignature": err, + }) + return + } + dg1, err := getDataGroup(encapsulatedContent, 0) if err != nil { log.WithError(err).Error("failed to get data group") @@ -152,18 +178,23 @@ func Register(w http.ResponseWriter, r *http.Request) { return } - // Since circuit is using 31 bits of dg1, we need to truncate it to last 31 bytes - dg1Truncated := dg1[len(dg1)-31:] - - if !bytes.Equal(dg1Truncated, proofDg1Decimal.Bytes()) { - log.Error("proof contains foreign data group 1") - jsonError = problems.BadRequest(validation.Errors{ - "zk_proof": errors.New("proof contains foreign data group 1"), - }) - return + dg1Truncated := dg1 + if len(dg1) > 31 { + // Since circuit is using 31 bits of dg1, we need to truncate it to last 31 bytes + dg1Truncated = dg1[len(dg1)-31:] } + _ = dg1Truncated + _ = proofDg1Decimal + + //if !bytes.Equal(dg1Truncated, proofDg1Decimal.Bytes()) { + // log.Error("proof contains foreign data group 1") + // jsonError = problems.BadRequest(validation.Errors{ + // "zk_proof": errors.New("proof contains foreign data group 1"), + // }) + // return + //} - err = verifySod(signedAttributes, encapsulatedContent, slaveSignature, cert, algorithmPair, cfg) + err = verifySod(signedAttributes, encapsulatedContent, slaveSignature.Bytes, cert, algorithmPair, cfg) if err != nil { var sodError *types.SodError errors2.As(err, &sodError) diff --git a/internal/types/signature_algorithm.go b/internal/types/signature_algorithm.go index ae57a0c..c22664a 100644 --- a/internal/types/signature_algorithm.go +++ b/internal/types/signature_algorithm.go @@ -3,13 +3,10 @@ package types import ( "crypto" "crypto/ecdsa" - "crypto/elliptic" "crypto/rsa" "fmt" "hash" - "reflect" - "github.com/keybase/go-crypto/brainpool" "github.com/rarimo/passport-identity-provider/internal/algorithms" "gitlab.com/distributed_lab/logan/v3/errors" ) @@ -48,8 +45,8 @@ func GeneralVerify(publicKey interface{}, hash []byte, signature []byte, algo Al return ErrInvalidPublicKey{Expected: algo.SignatureAlgorithm} } - if err := verifyECDSA(ecdsaKey, hash, signature); err != nil { - return errors.Wrap(err, "failed to verify ECDSA signature") + if err := algorithms.VerifyECDSA(hash, signature, ecdsaKey); err != nil { + return fmt.Errorf("failed to verify ECDSA signature with curve %s: %w", ecdsaKey.Curve.Params().Name, err) } default: return errors.New("unsupported signature algorithm") @@ -91,22 +88,3 @@ func getCryptoHash(hashAlgorithm HashAlgorithm) crypto.Hash { return 0 } } - -func verifyECDSA(ecdsaKey *ecdsa.PublicKey, hash []byte, signature []byte) error { - //print type of ecdsaKey.Curve - fmt.Println(reflect.TypeOf(ecdsaKey.Curve)) - switch ecdsaKey.Curve { - case elliptic.P224(), elliptic.P256(), elliptic.P384(), elliptic.P521(): - if !ecdsa.VerifyASN1(ecdsaKey, hash, signature) { - return errors.New("ECDSA verification failed") - } - case brainpool.P256r1(), brainpool.P384r1(), brainpool.P512r1(), - brainpool.P256t1(), brainpool.P384t1(), brainpool.P512t1(): - if err := algorithms.VerifyBrainpool(hash, signature, ecdsaKey); err != nil { - return errors.Wrap(err, "failed to verify brainpool signature") - } - default: - return errors.New("unsupported curve") - } - return nil -}