This repository has been archived by the owner on Feb 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
ardit
authored and
ardit
committed
Dec 9, 2019
0 parents
commit 065508d
Showing
15 changed files
with
1,732 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, build with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
.idea | ||
|
||
*.yaml | ||
*.toml | ||
|
||
*.pem |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Dana Sangu | ||
|
||
## Usage blueprint | ||
|
||
1. There is a type named `Client` (`dana.Client`) that should be instantiated through `NewClient` which hold any possible setting to the library. | ||
2. There is a gateway classes which you will be using depending on whether you used. The gateway type need a Client instance. | ||
3. All Header field is handled by this library | ||
4. There's also VerifySignature to verify whether the signature response/request is valid. | ||
5. Replace `.sample` files to your own credential. | ||
|
||
## Example | ||
|
||
```go | ||
danaClient := dana.NewClient() | ||
danaClient.BaseUrl = "DANA_BASE_URL", | ||
--- | ||
--- | ||
|
||
coreGateway := dana.CoreGateway{ | ||
Client: danaClient, | ||
} | ||
|
||
body := &dana.RequestBody{ | ||
Order: {}, | ||
MerchantId: "MERCHANT_ID", | ||
--- | ||
--- | ||
--- | ||
} | ||
|
||
res, _ := coreGateway.Order(req) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package dana | ||
|
||
import ( | ||
"encoding/json" | ||
"github.com/tidwall/gjson" | ||
"io" | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"os" | ||
"time" | ||
) | ||
|
||
// Client struct | ||
type Client struct { | ||
BaseUrl string | ||
Version string | ||
ClientId string | ||
ClientSecret string | ||
PrivateKey []byte | ||
PublicKey []byte | ||
LogLevel int | ||
Logger *log.Logger | ||
SignatureEnabled bool | ||
} | ||
|
||
// NewClient : this function will always be called when the library is in use | ||
func NewClient() Client { | ||
return Client{ | ||
// LogLevel is the logging level used by the Dana library | ||
// 0: No logging | ||
// 1: Errors only | ||
// 2: Errors + informational (default) | ||
// 3: Errors + informational + debug | ||
LogLevel: 2, | ||
Logger: log.New(os.Stderr, "", log.LstdFlags), | ||
SignatureEnabled: true, | ||
} | ||
} | ||
|
||
// ===================== HTTP CLIENT ================================================ | ||
var defHTTPTimeout = 15 * time.Second | ||
var httpClient = &http.Client{Timeout: defHTTPTimeout} | ||
|
||
// NewRequest : send new request | ||
func (c *Client) NewRequest(method string, fullPath string, headers map[string]string, body io.Reader) (*http.Request, error) { | ||
logLevel := c.LogLevel | ||
logger := c.Logger | ||
|
||
req, err := http.NewRequest(method, fullPath, body) | ||
if err != nil { | ||
if logLevel > 0 { | ||
logger.Println("Request creation failed: ", err) | ||
} | ||
return nil, err | ||
} | ||
|
||
if headers != nil { | ||
for k, vv := range headers { | ||
req.Header.Set(k, vv) | ||
} | ||
} | ||
|
||
return req, nil | ||
} | ||
|
||
// ExecuteRequest : execute request | ||
func (c *Client) ExecuteRequest(req *http.Request, v interface{}) error { | ||
logLevel := c.LogLevel | ||
logger := c.Logger | ||
|
||
if logLevel > 1 { | ||
logger.Println("Request ", req.Method, ": ", req.URL.Host, req.URL.Path) | ||
} | ||
|
||
start := time.Now() | ||
res, err := httpClient.Do(req) | ||
if err != nil { | ||
if logLevel > 0 { | ||
logger.Println("Cannot send request: ", err) | ||
} | ||
return err | ||
} | ||
defer res.Body.Close() | ||
|
||
if logLevel > 2 { | ||
logger.Println("Completed in ", time.Since(start)) | ||
} | ||
|
||
if err != nil { | ||
if logLevel > 0 { | ||
logger.Println("Request failed: ", err) | ||
} | ||
return err | ||
} | ||
|
||
resBody, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
if logLevel > 0 { | ||
logger.Println("Cannot read response body: ", err) | ||
} | ||
return err | ||
} | ||
|
||
if logLevel > 2 { | ||
logger.Println("Dana response: ", string(resBody)) | ||
} | ||
|
||
if v != nil && res.StatusCode == 200 { | ||
if err = json.Unmarshal(resBody, v); err != nil { | ||
return err | ||
} | ||
|
||
if c.SignatureEnabled { | ||
response := gjson.Get(string(resBody), "response") | ||
signature := gjson.Get(string(resBody), "signature") | ||
|
||
err := verifySignature(response.String(), signature.String(), c.PublicKey) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Call the Dana API at specific `path` using the specified HTTP `method`. The result will be | ||
// given to `v` if there is no error. If any error occurred, the return of this function is the error | ||
// itself, otherwise nil. | ||
func (c *Client) Call(method, path string, header map[string]string, body io.Reader, v interface{}) error { | ||
req, err := c.NewRequest(method, path, header, body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.ExecuteRequest(req, v) | ||
} | ||
|
||
// ===================== END HTTP CLIENT ================================================ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package dana | ||
|
||
import ( | ||
"crypto" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/sha256" | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/json" | ||
"encoding/pem" | ||
"errors" | ||
"fmt" | ||
"log" | ||
) | ||
|
||
const ( | ||
TYPE_ORDER = "ORDER" | ||
TYPE_PAY_NOTIFY = "PAY_NOTIFY" | ||
) | ||
|
||
func generateSignature(req interface{}, privateKey []byte) (sig string, err error) { | ||
signer, err := parsePrivateKey(privateKey) | ||
if err != nil { | ||
err = fmt.Errorf("signer is damaged: %v", err) | ||
return | ||
} | ||
plan, err := json.Marshal(req) | ||
if err != nil { | ||
err = fmt.Errorf("failed to marshal request: %v", err) | ||
return | ||
} | ||
signed, err := signer.Sign(plan) | ||
if err != nil { | ||
err = fmt.Errorf("could not sign request: %v", err) | ||
} | ||
sig = base64.StdEncoding.EncodeToString(signed) | ||
return | ||
} | ||
|
||
// parsePrivateKey parses a PEM encoded private key. | ||
func parsePrivateKey(pemBytes []byte) (Signer, error) { | ||
block, _ := pem.Decode(pemBytes) | ||
if block == nil { | ||
return nil, errors.New("ssh: no key found") | ||
} | ||
|
||
var rawkey interface{} | ||
switch block.Type { | ||
case "RSA PRIVATE KEY": | ||
rsa, err := x509.ParsePKCS1PrivateKey(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
rawkey = rsa | ||
default: | ||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) | ||
} | ||
return newSignerFromKey(rawkey) | ||
} | ||
|
||
func verifySignature(data string, sig string, publicKey []byte) error { | ||
parser, perr := parsePublicKey(publicKey) | ||
if perr != nil { | ||
log.Printf("could load public key: %v", perr) | ||
} | ||
|
||
ds, _ := base64.StdEncoding.DecodeString(sig) | ||
return parser.Unsign([]byte(data), ds) | ||
} | ||
|
||
// parsePublicKey parses a PEM encoded private key. | ||
func parsePublicKey(pemBytes []byte) (Unsigner, error) { | ||
block, _ := pem.Decode(pemBytes) | ||
if block == nil { | ||
return nil, errors.New("ssh: no key found") | ||
} | ||
|
||
var rawkey interface{} | ||
switch block.Type { | ||
case "PUBLIC KEY": | ||
rsa, err := x509.ParsePKIXPublicKey(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
rawkey = rsa | ||
default: | ||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) | ||
} | ||
|
||
return newUnsignerFromKey(rawkey) | ||
} | ||
|
||
// A Signer is can create signatures that verify against a public key. | ||
type Signer interface { | ||
// Sign returns raw signature for the given data. This method | ||
// will apply the hash specified for the keytype to the data. | ||
Sign(data []byte) ([]byte, error) | ||
} | ||
|
||
// A Signer is can create signatures that verify against a public key. | ||
type Unsigner interface { | ||
// Sign returns raw signature for the given data. This method | ||
// will apply the hash specified for the keytype to the data. | ||
Unsign(data []byte, sig []byte) error | ||
} | ||
|
||
func newSignerFromKey(k interface{}) (Signer, error) { | ||
var sshKey Signer | ||
switch t := k.(type) { | ||
case *rsa.PrivateKey: | ||
sshKey = &rsaPrivateKey{t} | ||
default: | ||
return nil, fmt.Errorf("ssh: unsupported key type %T", k) | ||
} | ||
return sshKey, nil | ||
} | ||
|
||
func newUnsignerFromKey(k interface{}) (Unsigner, error) { | ||
var sshKey Unsigner | ||
switch t := k.(type) { | ||
case *rsa.PublicKey: | ||
sshKey = &rsaPublicKey{t} | ||
default: | ||
return nil, fmt.Errorf("ssh: unsupported key type %T", k) | ||
} | ||
return sshKey, nil | ||
} | ||
|
||
type rsaPublicKey struct { | ||
*rsa.PublicKey | ||
} | ||
|
||
type rsaPrivateKey struct { | ||
*rsa.PrivateKey | ||
} | ||
|
||
// Sign signs data with rsa-sha256 | ||
func (r *rsaPrivateKey) Sign(data []byte) ([]byte, error) { | ||
h := sha256.New() | ||
h.Write(data) | ||
d := h.Sum(nil) | ||
return rsa.SignPKCS1v15(rand.Reader, r.PrivateKey, crypto.SHA256, d) | ||
} | ||
|
||
// Unsign verifies the message using a rsa-sha256 signature | ||
func (r *rsaPublicKey) Unsign(message []byte, sig []byte) error { | ||
h := sha256.New() | ||
h.Write(message) | ||
d := h.Sum(nil) | ||
return rsa.VerifyPKCS1v15(r.PublicKey, crypto.SHA256, d, sig) | ||
} |
Oops, something went wrong.