Skip to content

Commit

Permalink
Add #616 Review signature workflow in Consent Records
Browse files Browse the repository at this point in the history
  • Loading branch information
albinpa authored and georgepadayatti committed Jan 17, 2024
1 parent f5bebef commit 50b3b4d
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ func ServiceCreateBlankSignature(w http.ResponseWriter, r *http.Request) {
darRepo := daRecord.DataAgreementRecordRepository{}
darRepo.Init(organisationId)

_, err := darRepo.Get(dataAgreementRecordId)
if err != nil {
m := "Failed to fetch data agreement record"
common.HandleErrorV2(w, http.StatusBadRequest, m, err)
return
}

// Get latest revision for data agreement record
daRecordRevision, err := revision.GetLatestByObjectIdAndSchemaName(dataAgreementRecordId, config.DataAgreementRecord)
if err != nil {
Expand All @@ -36,7 +43,7 @@ func ServiceCreateBlankSignature(w http.ResponseWriter, r *http.Request) {
return
}
// create signature for data agreement record
toBeCreatedSignature, err := signature.CreateSignatureForObject("revision", daRecordRevision.Id, false, daRecordRevision, false, signature.Signature{})
toBeCreatedSignature, err := signature.CreateSignatureForConsentRecord("Revision", daRecordRevision.Id, false, daRecordRevision.SerializedSnapshot, daRecordRevision.SerializedHash, signature.Signature{})
if err != nil {
m := fmt.Sprintf("Failed to create signature for data agreement record: %v", dataAgreementRecordId)
common.HandleErrorV2(w, http.StatusInternalServerError, m, err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,31 @@ func createPairedDataAgreementRecord(dataAgreementId string, rev revision.Revisi
newDaRecord.DataAgreementRevisionId = rev.Id
newDaRecord.IndividualId = individualId
newDaRecord.OptIn = true
newDaRecord.State = config.Unsigned
newDaRecord.State = config.Signed

return newDaRecord
}

// createSignatureFromCreateSignatureRequestBody
func createSignatureFromCreateSignatureRequestBody(toBeCreatedSignature signature.Signature, signatureReq signature.Signature) signature.Signature {

toBeCreatedSignature.Payload = signatureReq.Payload
toBeCreatedSignature.Signature = signatureReq.Signature
toBeCreatedSignature.VerificationMethod = signatureReq.VerificationMethod
toBeCreatedSignature.VerificationPayload = signatureReq.VerificationPayload
toBeCreatedSignature.VerificationPayloadHash = signatureReq.VerificationPayloadHash
toBeCreatedSignature.VerificationArtifact = signatureReq.VerificationArtifact
toBeCreatedSignature.VerificationSignedBy = signatureReq.VerificationSignedBy
toBeCreatedSignature.VerificationSignedAs = signatureReq.VerificationSignedAs
toBeCreatedSignature.VerificationJwsHeader = signatureReq.VerificationJwsHeader
toBeCreatedSignature.Timestamp = signatureReq.Timestamp
toBeCreatedSignature.SignedWithoutObjectReference = signatureReq.SignedWithoutObjectReference
toBeCreatedSignature.ObjectType = signatureReq.ObjectType
toBeCreatedSignature.ObjectReference = signatureReq.ObjectReference

return toBeCreatedSignature
}

type dataAgreementRecordReq struct {
Id string `json:"id" bson:"_id,omitempty"`
DataAgreementId string `json:"dataAgreementId" valid:"required"`
Expand Down Expand Up @@ -123,26 +143,41 @@ func ServiceCreatePairedDataAgreementRecord(w http.ResponseWriter, r *http.Reque

newDataAgreementRecord := createPairedDataAgreementRecord(dataAgreement.Id, dataAgreementRevision, individual.Id)

var toBeCreatedSignature signature.Signature
toBeCreatedSignature.Id = primitive.NewObjectID().Hex()

// verify signature
err = signature.VerifySignature(dataAgreementRecordReq.Signature.Signature, dataAgreementRecordReq.Signature.VerificationSignedBy)
if err != nil {
m := "Failed to verify signature for consent record"
common.HandleErrorV2(w, http.StatusBadRequest, m, err)
return
}

// create signature for data agreement record
toBeCreatedSignature = createSignatureFromCreateSignatureRequestBody(toBeCreatedSignature, dataAgreementRecordReq.Signature)
if err != nil {
m := "Failed to create signature for consent record"
common.HandleErrorV2(w, http.StatusBadRequest, m, err)
return
}

dataAgreementRecord := newDataAgreementRecord
dataAgreementRecord.OrganisationId = organisationId
currentSignature := dataAgreementRecordReq.Signature
dataAgreementRecord.Id = primitive.NewObjectID().Hex()
currentSignature.Id = primitive.NewObjectID().Hex()
dataAgreementRecord.SignatureId = currentSignature.Id
dataAgreementRecord.SignatureId = toBeCreatedSignature.Id

newRecordRevision, err := revision.CreateRevisionForDataAgreementRecord(dataAgreementRecord, individualId)
if err != nil {
m := fmt.Sprintf("Failed to create new revision for dataAgreementRecord: %v", dataAgreementRecord.Id)
common.HandleErrorV2(w, http.StatusInternalServerError, m, err)
return
}
// create signature for data agreement record
toBeCreatedSignature, err := signature.CreateSignatureForObject("revision", newRecordRevision.Id, false, newRecordRevision, true, currentSignature)
if err != nil {
m := fmt.Sprintf("Failed to create signature for data agreement record: %v", dataAgreementRecord.Id)
common.HandleErrorV2(w, http.StatusInternalServerError, m, err)
return
}

newRecordRevision.SerializedSnapshot = toBeCreatedSignature.VerificationPayload
newRecordRevision.SerializedHash = toBeCreatedSignature.VerificationPayloadHash
newRecordRevision.SignedWithoutObjectId = true
toBeCreatedSignature.SignedWithoutObjectReference = true

savedDataAgreementRecord, err := darRepo.Add(dataAgreementRecord)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func ServiceUpdateDataAgreementRecord(w http.ResponseWriter, r *http.Request) {
return
}
toBeUpdatedDaRecord.OptIn = optIn
toBeUpdatedDaRecord.State = config.Unsigned

currentDataAgreementRevision, err := revision.GetLatestByObjectIdAndSchemaName(toBeUpdatedDaRecord.DataAgreementId, config.DataAgreement)
if err != nil {
Expand Down
23 changes: 20 additions & 3 deletions internal/handler/v2/service/service_update_signature_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"strings"

"github.com/asaskevich/govalidator"
"github.com/bb-consent/api/internal/common"
Expand All @@ -13,6 +14,7 @@ import (
"github.com/bb-consent/api/internal/revision"
"github.com/bb-consent/api/internal/signature"
"github.com/gorilla/mux"
"go.mongodb.org/mongo-driver/bson/primitive"
)

// createSignatureFromUpdateSignatureRequestBody
Expand Down Expand Up @@ -83,10 +85,23 @@ func ServiceUpdateSignatureObject(w http.ResponseWriter, r *http.Request) {
return
}

toBeUpdatedSignatureObject, err := signature.Get(toBeUpdatedDaRecord.SignatureId)
var toBeUpdatedSignatureObject signature.Signature

if len(strings.TrimSpace(toBeUpdatedDaRecord.SignatureId)) > 1 {
toBeUpdatedSignatureObject, err = signature.Get(toBeUpdatedDaRecord.SignatureId)
if err != nil {
m := "Failed to fetch signature for data agreement record"
common.HandleErrorV2(w, http.StatusBadRequest, m, err)
return
}
} else {
toBeUpdatedSignatureObject.Id = primitive.NewObjectID().Hex()
}

err = signature.VerifySignature(signatureReq.Signature.Signature, signatureReq.Signature.VerificationSignedBy)
if err != nil {
m := "Failed to fetch signature for data agreement record"
common.HandleErrorV2(w, http.StatusInternalServerError, m, err)
m := "Failed to verify signature for consent record"
common.HandleErrorV2(w, http.StatusBadRequest, m, err)
return
}

Expand Down Expand Up @@ -118,6 +133,8 @@ func ServiceUpdateSignatureObject(w http.ResponseWriter, r *http.Request) {
common.HandleErrorV2(w, http.StatusInternalServerError, m, err)
return
}
newRevision.SerializedSnapshot = savedSignature.VerificationPayload
newRevision.SerializedHash = savedSignature.VerificationPayloadHash

// Save the revision to db
_, err = revision.Add(newRevision)
Expand Down
100 changes: 100 additions & 0 deletions internal/jwk/jwk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package jwk

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"math/big"
)

// JWK represents a JSON Web Key
type JWK struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
PublicKey *ecdsa.PublicKey `json:"-"`
}

// FromECPublicKey creates JWK from elliptic curve public key
func (obj *JWK) FromECPublicKey(publicKey *ecdsa.PublicKey) *JWK {
return &JWK{
Kty: "EC",
Crv: "P-256",
X: encodeBase64URL(publicKey.X.Bytes()),
Y: encodeBase64URL(publicKey.Y.Bytes()),
PublicKey: &ecdsa.PublicKey{},
}
}

// GenerateECKey generate ECDSA key pair using secp256r1 curve
func (obj *JWK) GenerateECKey() *ecdsa.PrivateKey {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatalf("Error generating ECDSA key pair: %v", err)
}

obj.PublicKey = &privateKey.PublicKey
return privateKey
}

// ToJSON to json string
func (obj *JWK) ToJSON() string {
jwk := obj.FromECPublicKey(obj.PublicKey)
jwkJSON, err := json.MarshalIndent(jwk, "", " ")
if err != nil {
log.Fatalf("Error marshaling JWK to JSON: %v", err)
}

return string(jwkJSON)
}

// ToECPublicKey to ECDSA public key
func (obj *JWK) ToECPublicKey() *ecdsa.PublicKey {
curve := elliptic.P256() // P-256 curve is assumed, adjust as needed
xBytes, err := decodeBase64URL(obj.X)
if err != nil {
log.Fatalf("failed to decode x parameter: %v", err)
}

yBytes, err := decodeBase64URL(obj.Y)
if err != nil {
log.Fatalf("failed to decode y parameter: %v", err)
}

pubKey := &ecdsa.PublicKey{
Curve: curve,
X: new(big.Int).SetBytes(xBytes),
Y: new(big.Int).SetBytes(yBytes),
}

return pubKey
}

// FromJSON from json string to JWK
func FromJSON(jwkString string) JWK {
var jwk JWK
err := json.Unmarshal([]byte(jwkString), &jwk)
if err != nil {
log.Fatal("Failed to unmarshal JWK:", err)
}
return jwk
}

// decodeBase64URL use base64.URLEncoding to decode base64 URL-encoded strings
func decodeBase64URL(input string) ([]byte, error) {
decoded, err := base64.RawURLEncoding.DecodeString(input)
if err != nil {
return nil, fmt.Errorf("base64 URL decoding failed: %w", err)
}
return decoded, nil
}

// encodeBase64URL encodes the input bytes to base64url format
func encodeBase64URL(data []byte) string {
return base64.RawURLEncoding.EncodeToString(data)
}
36 changes: 36 additions & 0 deletions internal/jws/jws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package jws

import (
"fmt"

"github.com/bb-consent/api/internal/jwk"
"github.com/go-jose/go-jose/v3"
)

// JWS represents a JSON Web Signature (JWS)
type JWS struct {
Claims string `json:"-"`
Key jwk.JWK `json:"-"`
Signature string `json:"-"`
}

// Verify verify JSON web signature (JWS)
func (obj *JWS) Verify() error {
// Create EC public key
pubKey := obj.Key.ToECPublicKey()
// Deserialise to JWS
jws, err := jose.ParseSigned(obj.Signature)
if err != nil {
return err
}
// Verify signature and return decoded payload
payload, err := jws.Verify(pubKey)
if err != nil {
return err
}
// Print decoded payload
fmt.Println("Signature verified.")
fmt.Printf("\nPayload: \n\n%v\n", string(payload))

return nil
}
46 changes: 19 additions & 27 deletions internal/signature/signature.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package signature

import (
"encoding/json"
"time"

"github.com/bb-consent/api/internal/common"
"github.com/bb-consent/api/internal/jwk"
"github.com/bb-consent/api/internal/jws"
)

type Signature struct {
Expand Down Expand Up @@ -34,40 +34,32 @@ func (s *Signature) Init(ObjectType string, ObjectReference string, SignedWithou
}

// CreateSignature
func (s *Signature) CreateSignature(VerificationPayload interface{}, IsPayload bool) error {
func (s *Signature) CreateSignature(serialisedSnapshot string, serialisedHash string) error {

// Verification Payload
verificationPayloadSerialised, err := json.Marshal(VerificationPayload)
if err != nil {
return err
}
s.VerificationPayload = string(verificationPayloadSerialised)

// Serialised hash using SHA-1
s.VerificationPayloadHash, err = common.CalculateSHA1(string(verificationPayloadSerialised))
if err != nil {
return err
}

if IsPayload {
// Payload
payload, err := json.Marshal(s)
if err != nil {
return err
}
s.Payload = string(payload)
}
s.VerificationPayload = serialisedSnapshot
s.VerificationPayloadHash = serialisedHash

return nil

}

// CreateSignatureForPolicy
func CreateSignatureForObject(ObjectType string, ObjectReference string, SignedWithoutObjectReference bool, VerificationPayload interface{}, IsPayload bool, signature Signature) (Signature, error) {
// CreateSignatureForConsentRecord
func CreateSignatureForConsentRecord(ObjectType string, ObjectReference string, SignedWithoutObjectReference bool, serialisedSnapshot string, serialisedHash string, signature Signature) (Signature, error) {

// Create signature
signature.Init(ObjectType, ObjectReference, SignedWithoutObjectReference)
err := signature.CreateSignature(VerificationPayload, IsPayload)
err := signature.CreateSignature(serialisedSnapshot, serialisedHash)

return signature, err
}

// VerifySignature
func VerifySignature(signature string, publicKey string) error {

jwsObj := jws.JWS{Key: jwk.FromJSON(publicKey), Signature: signature}
err := jwsObj.Verify()
if err != nil {
return err
}
return nil
}
2 changes: 1 addition & 1 deletion resources/config

0 comments on commit 50b3b4d

Please sign in to comment.