From 06958c80c5d3aca80e23eef6adb741b02c62fe0a Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Wed, 19 Jan 2022 23:45:53 -0800 Subject: [PATCH] refactor message util --- doc/api.oa3 | 92 ++++++++++----- net/server/internal/api_authorize.go | 60 ++-------- net/server/internal/appValues.go | 6 + net/server/internal/messageUtil.go | 143 ++++++++++++++++++++++++ net/server/internal/models.go | 79 ++++++++----- net/server/internal/ucAttachApp_test.go | 29 ++--- 6 files changed, 282 insertions(+), 127 deletions(-) create mode 100644 net/server/internal/messageUtil.go diff --git a/doc/api.oa3 b/doc/api.oa3 index 8f8599f7..bcea40dc 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -4408,74 +4408,110 @@ components: Authenticate: type: object required: - - guid - token - - timestamp properties: - guid: - type: string token: type: string - timestamp: + + Identity: + type: object + required: + - revision + - version + - node + properties: + revision: type: integer format: int64 + handle: + type: string + name: + type: string + description: + type: string + location: + type: string + image: + type: string + format: base64 encoded image + version: + type: string + node: + type: string Connect: type: object required: - - requestorId - - requestedId - - timestamp - - profile + - contact - token - contentRevision properties: - requestorcardId: + contact: type: string - requestedcardId: - type: string - timestamp: - type: integer - format: int64 - profile: - $ref: '#/components/schemas/Profile' token: type: string contentRevision: type: integer format: int64 + profileRevision: + type: integer + format: int64 + handle: + type: string + name: + type: string + description: + type: string + location: + type: string + image: + type: string + format: base64 encoded image + version: + type: string + node: + type: string Disconnect: type: object required: - - requestorId - - requestedId - - timestamp + - contact properties: - requestorId: + contact: type: string - requestedId: + + SignedData: + type: object + required: + - guid + - timestamp + - messageType + - value + properties: + guid: type: string - timestamp: + timestamp: type: integer format: int64 + messageType: + type: string + enum: [Connect, Disconnect, Identity, Authenticate] + value: + type: string + format: json string of Connect, Disconnect, Authenticate, or Profile DataMessage: type: object required: - message - - messageType - keyType - publicKey - signature - signatureType properties: - messageType: - type: string - enum: [Connect, Disconnect, Profile, Authenticate] message: type: string - format: base64 encoded object + format: base64 encoded json string of SignedData keyType: type: string enum: [RSA4096, RSA2048] diff --git a/net/server/internal/api_authorize.go b/net/server/internal/api_authorize.go index 39ef953a..c68012f5 100644 --- a/net/server/internal/api_authorize.go +++ b/net/server/internal/api_authorize.go @@ -1,74 +1,38 @@ package databag import ( - "crypto" - "crypto/rand" - "crypto/sha256" - "crypto/rsa" "net/http" - "encoding/json" - "encoding/base64" - "time" ) func Authorize(w http.ResponseWriter, r *http.Request) { account, res := BearerAppToken(r, true); if res != nil { - w.WriteHeader(http.StatusUnauthorized) + ErrResponse(w, http.StatusUnauthorized, res) return } if account.Disabled { - w.WriteHeader(http.StatusGone); + ErrResponse(w, http.StatusGone, res) return } + detail := account.AccountDetail // extract token from body var token string - err := ParseRequest(r, w, &token) - if err != nil { - w.WriteHeader(http.StatusBadRequest) + res = ParseRequest(r, w, &token) + if res != nil { + ErrResponse(w, http.StatusBadRequest, res) return } - // load details to sign data - if account.AccountDetail.KeyType != APP_RSA2048 && account.AccountDetail.KeyType != APP_RSA4096 { - w.WriteHeader(http.StatusServiceUnavailable) + // generate auth DataMessage + auth := Authenticate{ Token: token } + msg, res := WriteDataMessage(detail.PrivateKey, detail.PublicKey, detail.KeyType, + APP_SIGNPKCS1V15, account.Guid, APP_MSGAUTHENTICATE, &auth) + if res != nil { + ErrResponse(w, http.StatusInternalServerError, res) return } - privateKey, res := ParseRsaPrivateKeyFromPemStr(account.AccountDetail.PrivateKey); - if res != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - // generate message - auth := Authenticate{ - Guid: account.Guid, - Token: token, - Timestamp: time.Now().Unix(), - } - var data []byte - data, err = json.Marshal(auth); - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - hash := sha256.Sum256(data); - var signature []byte - signature, err = rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash[:]) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - msg := DataMessage{ - MessageType: "authenticate", - Message: base64.StdEncoding.EncodeToString([]byte(data)), - KeyType: account.AccountDetail.KeyType, - PublicKey: base64.StdEncoding.EncodeToString([]byte(account.AccountDetail.PublicKey)), - Signature: base64.StdEncoding.EncodeToString(signature), - SignatureType: "PKCS1v15", - } WriteResponse(w, msg) } diff --git a/net/server/internal/appValues.go b/net/server/internal/appValues.go index 2d00a201..77ccfbcf 100644 --- a/net/server/internal/appValues.go +++ b/net/server/internal/appValues.go @@ -7,3 +7,9 @@ const APP_CREATEEXPIRE = 86400 const APP_KEYSIZE = 4096 const APP_RSA4096 = "RSA4096" const APP_RSA2048 = "RSA2048" +const APP_SIGNPKCS1V15 = "PKCS1v15" +const APP_SIGNPSS = "PSS" +const APP_MSGAUTHENTICATE = "authenticate" +const APP_MSGIDENTITY = "identity" +const APP_MSGCONNECT = "connect" +const APP_MSGDISCONNECT = "disconnect" diff --git a/net/server/internal/messageUtil.go b/net/server/internal/messageUtil.go new file mode 100644 index 00000000..7b00bfce --- /dev/null +++ b/net/server/internal/messageUtil.go @@ -0,0 +1,143 @@ +package databag + +import ( + "errors" + "crypto" + "crypto/rand" + "crypto/sha256" + "crypto/rsa" + "encoding/json" + "encoding/hex" + "encoding/base64" + "time" +) + +func ReadDataMessage(msg *DataMessage, obj interface{}) (string, string, int64, error) { + + var data []byte + var hash [32]byte + var err error + var publicKey *rsa.PublicKey + + if msg.KeyType != APP_RSA4096 && msg.KeyType != APP_RSA2048 { + return "", "", 0, errors.New("unsupported key type") + } + + // extract public key + data, err = base64.StdEncoding.DecodeString(msg.PublicKey) + if err != nil { + return "", "", 0, err + } + publicKey, err = ParseRsaPublicKeyFromPemStr(string(data)) + if err != nil { + return "", "", 0, err + } + + // compute guid + hash = sha256.Sum256(data) + guid := hex.EncodeToString(hash[:]) + + // extract signature + data, err = base64.StdEncoding.DecodeString(msg.Signature) + if err != nil { + return "", "", 0, err + } + signature := data + + // verify hash + data, err = base64.StdEncoding.DecodeString(msg.Message) + if err != nil { + return "", "", 0, err + } + hash = sha256.Sum256(data) + if msg.SignatureType == APP_SIGNPKCS1V15 { + err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], signature) + if err != nil { + return "", "", 0, err + } + } else if msg.SignatureType == APP_SIGNPSS { + err = rsa.VerifyPSS(publicKey, crypto.SHA256, hash[:], signature, nil) + if err != nil { + return "", "", 0, err + } + } else { + return "", "", 0, errors.New("unsupported signature type") + } + + // extract data + var signedData SignedData + err = json.Unmarshal(data, &signedData); + if err != nil { + return "", "", 0, err + } + + // validate signer + if signedData.Guid != guid { + return "", "", 0, errors.New("invalid message source") + } + + // extract data + err = json.Unmarshal([]byte(signedData.Value), obj) + if err != nil { + return "", "", 0, err + } + + return guid, signedData.MessageType, signedData.Timestamp, nil +} + +func WriteDataMessage(privateKey string, publicKey string, keyType string, + signType string, guid string, messageType string, obj interface{}) (*DataMessage, error) { + + var data []byte + var hash [32]byte + var err error + var private *rsa.PrivateKey + + // create message to sign + data, err = json.Marshal(obj) + if err != nil { + return nil, err + } + var signedData SignedData + signedData.Guid = guid + signedData.Timestamp = time.Now().Unix() + signedData.MessageType = messageType + signedData.Value = string(data) + data, err = json.Marshal(&signedData) + message := base64.StdEncoding.EncodeToString(data) + + if keyType != APP_RSA2048 && keyType != APP_RSA4096 { + return nil, errors.New("unsupported key type") + } + + // get private key + private, err = ParseRsaPrivateKeyFromPemStr(privateKey) + if err != nil { + return nil, err + } + key := base64.StdEncoding.EncodeToString([]byte(publicKey)) + + // compute signature + hash = sha256.Sum256(data) + if signType == APP_SIGNPKCS1V15 { + data, err = rsa.SignPKCS1v15(rand.Reader, private, crypto.SHA256, hash[:]) + } else if signType == APP_SIGNPSS { + data, err = rsa.SignPSS(rand.Reader, private, crypto.SHA256, hash[:], nil) + } else { + return nil, errors.New("unsupported signature type") + } + signature := base64.StdEncoding.EncodeToString(data) + + dataMessage := DataMessage{ + PublicKey: key, + KeyType: keyType, + Signature: signature, + SignatureType: signType, + Message: message, + } + return &dataMessage, nil +} + + + + diff --git a/net/server/internal/models.go b/net/server/internal/models.go index 57a23650..4d3ddca4 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -68,12 +68,6 @@ type Asset struct { Status string `json:"status,omitempty"` } -type Authenticate struct { - Guid string `json:"guid"` - Token string `json:"token"` - Timestamp int64 `json:"timestamp"` -} - type Card struct { CardId string `json:"cardId"` CardProfile *CardProfile `json:"cardProfile"` @@ -107,15 +101,6 @@ type CardView struct { ContentRevision int64 `json:"contentRevision"` } -type Connect struct { - RequestorcardId string `json:"requestorcardId,omitempty"` - RequestedcardId string `json:"requestedcardId,omitempty"` - Timestamp int64 `json:"timestamp"` - Profile *Profile `json:"profile"` - Token string `json:"token"` - ContentRevision int64 `json:"contentRevision"` -} - type ContentArticlesBody struct { Labels []string `json:"labels"` Groups []string `json:"groups"` @@ -126,15 +111,6 @@ type ContentLabelsBody struct { Data string `json:"data"` } -type DataMessage struct { - MessageType string `json:"messageType"` - Message string `json:"message"` - KeyType string `json:"keyType"` - PublicKey string `json:"publicKey"` - Signature string `json:"signature"` - SignatureType string `json:"signatureType"` -} - type Dialogue struct { DialogueId string `json:"dialogueId"` DialogueRevison int64 `json:"dialogueRevison,omitempty"` @@ -156,12 +132,6 @@ type DialogueInsights struct { Status string `json:"status,omitempty"` } -type Disconnect struct { - RequestorId string `json:"requestorId"` - RequestedId string `json:"requestedId"` - Timestamp int64 `json:"timestamp"` -} - type Group struct { GroupId string `json:"groupId"` GroupRevision int64 `json:"groupRevision"` @@ -305,3 +275,52 @@ type Tunnel struct { Type_ string `json:"type"` Data string `json:"data,omitempty"` } + +type DataMessage struct { + Message string `json:"message"` + KeyType string `json:"keyType"` + PublicKey string `json:"publicKey"` + Signature string `json:"signature"` + SignatureType string `json:"signatureType"` +} + +type SignedData struct { + Guid string `json:"guid"` + Timestamp int64 `json:"timestamp"` + MessageType string `json:"messageType"` + Value string `json:"value"` +} + +type Identity struct { + Revision int64 `json:"revision"` + Handle string `json:"handle,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Location string `json:"location,omitempty"` + Image string `json:"image,omitempty"` + Version string `json:"version"` + Node string `json:"node"` +} + +type Connect struct { + Contact string `json:"contact"` + Token string `json:"token"` + ContentRevision int64 `json:"contentRevision"` + ProfileRevision int64 `json:"profileRevision,omitempty"` + Handle string `json:"handle,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Location string `json:"location,omitempty"` + Image string `json:"image,omitempty"` + Version string `json:"version,omitempty"` + Node string `json:"node,omitempty"` +} + +type Disconnect struct { + Contact string `json:"contact"` +} + +type Authenticate struct { + Token string `json:"token"` +} + diff --git a/net/server/internal/ucAttachApp_test.go b/net/server/internal/ucAttachApp_test.go index 8a70b792..b84ab160 100644 --- a/net/server/internal/ucAttachApp_test.go +++ b/net/server/internal/ucAttachApp_test.go @@ -2,12 +2,7 @@ package databag import ( "testing" - "encoding/hex" "encoding/json" - "encoding/base64" - "crypto/sha256" - "crypto/rsa" - "crypto" "time" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" @@ -56,24 +51,16 @@ func TestAttachAccount(t *testing.T) { assert.NoError(t, ReadResponse(w, &message)) // validate message - assert.Equal(t, "PKCS1v15", message.SignatureType) - var data []byte - var hash [32]byte - data, _ = base64.StdEncoding.DecodeString(message.PublicKey) - hash = sha256.Sum256(data) - guid := hex.EncodeToString(hash[:]) - publicKey, _ := ParseRsaPublicKeyFromPemStr(string(data)) - signature, _ := base64.StdEncoding.DecodeString(message.Signature) - data, _ = base64.StdEncoding.DecodeString(message.Message) - hash = sha256.Sum256(data) - assert.NoError(t, rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], signature)) var auth Authenticate - assert.NoError(t, json.Unmarshal(data,&auth)) - assert.Equal(t, "aabbccdd", auth.Token) - assert.Equal(t, guid, auth.Guid) + guid, msgType, ts, err := ReadDataMessage(&message, &auth) + if err != nil { + PrintMsg(err) + } cur := time.Now().Unix() - assert.GreaterOrEqual(t, cur, auth.Timestamp) - assert.Less(t, cur - 60, auth.Timestamp) + assert.GreaterOrEqual(t, cur, ts) + assert.Less(t, cur - 60, ts) + assert.Equal(t, "aabbccdd", auth.Token) + assert.Equal(t, msgType, APP_MSGAUTHENTICATE) // app connects websocket ws := getTestWebsocket()