github.com/456vv/valexa@v1.0.2-0.20200706152242-1fb922d71ce5/CheckRequestBody.go (about)

     1  package valexa
     2  
     3  import (
     4  	"net/http"
     5  	"io"
     6  	"fmt"
     7  	"net/url"
     8  	"path"
     9  	"strings"
    10  	"bytes"
    11  	"io/ioutil"
    12  	"os"
    13  	"encoding/pem"
    14  	"encoding/base64"
    15  	"encoding/json"
    16  	"crypto"
    17  	"crypto/sha1"
    18  	"crypto/x509"
    19  	"crypto/rsa"
    20  	"time"
    21  
    22  )
    23  
    24  
    25  
    26  //https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/developing-an-alexa-skill-as-a-web-service#hosting-a-custom-skill-as-a-web-service
    27  type checkRequestBody struct {
    28  	R 				*http.Request							//请求对象
    29  }
    30  
    31  func (T *checkRequestBody) echoRequest(echoApp *EchoApplication) (echoReq *EchoRequest, err error) {
    32  	err = json.NewDecoder(T.R.Body).Decode(&echoReq)
    33  	if err != nil {
    34  		return nil, fmt.Errorf("valexa: Body 数据结构无法解析, 错误的是(%s)", err)
    35  	}
    36  
    37  	//检查时间
    38  	if !echoReq.VerifyTimestamp(echoApp.ValidReqTimestamp) {
    39  		return nil, fmt.Errorf("valexa: 请求时间超出(>%ds),已经过时了。", echoApp.ValidReqTimestamp)
    40  	}
    41  	return echoReq, nil
    42  
    43  }
    44  
    45  func (T *checkRequestBody) verifyBody(echoApp *EchoApplication) (body io.Reader, err error) {
    46  
    47  	if T.R.Method != "POST" {
    48  		return nil, fmt.Errorf("valexa: 请求仅支持 POST 方法, 错误的是(%s)", T.R.Method)
    49  	}
    50  
    51  	certURL := T.R.Header.Get("SignatureCertChainUrl")
    52  
    53  	link, err := url.Parse(certURL)
    54  	if err != nil{
    55  		return nil, fmt.Errorf("valexa: 解析SignatureCertChainUrl地址路径失败, 错误的是(%s)", err)
    56  	}
    57  
    58  	if !strings.EqualFold(link.Scheme, "https") {
    59  		return nil, fmt.Errorf("valexa: 网址协议仅支持https, 错误的是(%s)", link.Scheme)
    60  	}
    61  
    62  	if !strings.EqualFold(link.Host, "s3.amazonaws.com")  && !strings.EqualFold(link.Host, "s3.amazonaws.com:443") {
    63  		return nil, fmt.Errorf("valexa: 网址host仅支持s3.amazonaws.com, 错误的是(%s)", link.Host)
    64  	}
    65  
    66  	if !strings.HasPrefix(path.Clean(link.Path) , "/echo.api/") {
    67  		return nil, fmt.Errorf("valexa: 网址Path前缀仅支持/echo.api/, 错误的是(%s)", link.Path)
    68  	}
    69  
    70  	//读取证书文件
    71  	name 		:= path.Base(link.Path)
    72  	filePath 	:= path.Join(echoApp.CertFolder, name)
    73  	certBody, err := ioutil.ReadFile(filePath)
    74  	if err != nil {
    75  		resp, err := http.Get(certURL)
    76  		if err != nil {
    77  			return nil, fmt.Errorf("valexa: 下载证书文件失败, 错误的是(%s)", err)
    78  		}
    79  		certBody, err = ioutil.ReadAll(resp.Body)
    80  		resp.Body.Close()
    81  		if err != nil {
    82  			return nil, fmt.Errorf("valexa: 读取文件失败, 错误的是(%s)", err)
    83  		}
    84  		osFile, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0755)
    85  		if err != nil {
    86  			return nil, fmt.Errorf("valexa: 创建文件失败, 错误的是(%s)", err)
    87  		}
    88  		n, err := osFile.Write(certBody)
    89  		osFile.Close()
    90  		if err != nil {
    91  			return nil, fmt.Errorf("valexa: 写入文件失败, 错误的是(%s)", err)
    92  		}
    93  		if len(certBody) != n {
    94  			os.Rename(filePath, filePath+".temp")
    95  			return nil, fmt.Errorf("valexa: 证书文件保存到本地不完整!")
    96  		}
    97  	}
    98  	if len(certBody) == 0 {
    99  		return nil, fmt.Errorf("valexa: 证书文件大小为 0")
   100  	}
   101  
   102  	//如果不能识别这个证书,需要重命名
   103  	var rename bool = true
   104  	defer func(){
   105  		if err != nil && rename {
   106  			os.Rename(filePath, filePath+".temp")
   107  		}
   108  	}()
   109  
   110  	var (
   111  		cCert	*x509.Certificate
   112  		rCert	*x509.Certificate
   113  	)
   114  
   115  	pemBlock, certBody := pem.Decode(certBody)
   116  	if pemBlock == nil {
   117  		return nil, fmt.Errorf("valexa: 无法解析证书PEM文件!")
   118  	}
   119  
   120  	x509Certificate, err := x509.ParseCertificate(pemBlock.Bytes)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("valexa: 无法解析证书PEM, 错误的是(%s)", err)
   123  	}
   124  	cCert = x509Certificate
   125  	for len(certBody)>0 {
   126  		pemBlock, certBody = pem.Decode(certBody)
   127  		if pemBlock == nil {
   128  			return nil, fmt.Errorf("valexa: 无法解析证书PEM文件!")
   129  		}
   130  		rCert, err = x509.ParseCertificate(pemBlock.Bytes)
   131  		if err != nil {
   132  			return nil, fmt.Errorf("valexa: 无法解析证书PEM, 错误的是(%s)", err)
   133  		}
   134  		if err := cCert.CheckSignatureFrom(rCert); err != nil {
   135  			return nil, fmt.Errorf("valexa: 无法验证证书链签名, 错误的是(%s)", err)
   136  		}
   137  		cCert = rCert
   138  	}
   139  //	Amazon 提供的证书链是不完整的,无法使用根证书验证自身
   140  //	所以这里注释
   141  //	if err := cCert.CheckSignatureFrom(cCert); err != nil {
   142  //		return nil, fmt.Errorf("根证书无法验证自身签名, 错误的是(%s)", err)
   143  //	}
   144  
   145  
   146  	if time.Now().Unix() < x509Certificate.NotBefore.Unix() || time.Now().Unix() > x509Certificate.NotAfter.Unix() {
   147  		return nil, fmt.Errorf("valexa: Amazon 证书已经过期!")
   148  	}
   149  
   150  	//检查证书签属名称
   151  	foundName := false
   152  	for _, altName := range x509Certificate.Subject.Names {
   153  		if altName.Value.(string) == "echo-api.amazon.com" {
   154  			foundName = true
   155  			break
   156  		}
   157  	}
   158  	if !foundName {
   159  		return nil, fmt.Errorf("valexa: Amazon 证书 Subject.names[].Value 没有检测到包含 echo-api.amazon.com 域名。")
   160  	}
   161  
   162  	//如果错误,不要命名证书文件
   163  	rename = false
   164  
   165  	//验证KEY
   166  	publicKey := x509Certificate.PublicKey
   167  	encryptedSig, err := base64.StdEncoding.DecodeString(T.R.Header.Get("Signature"))
   168  	if err != nil {
   169  		return nil, fmt.Errorf("valexa: 请求标头 Signature 无法识别, 错误的是(%s)", T.R.Header.Get("Signature"))
   170  	}
   171  
   172  	//读取Body, 和转化 HASH
   173  	var bodyBuf bytes.Buffer
   174  	hash := sha1.New()
   175  	ioReader := io.TeeReader(T.R.Body, &bodyBuf)
   176  	_, err = io.Copy(hash, ioReader)
   177  	T.R.Body.Close()
   178  	T.R.Body = ioutil.NopCloser(&bodyBuf)
   179  	if err != nil && err != io.ErrUnexpectedEOF {
   180  		return nil, fmt.Errorf("valexa: 读取 Body 数据转化成 sha1 HASH 出了问题, 错误的是(%s)", err)
   181  	}
   182  	
   183  	if err := rsa.VerifyPKCS1v15(publicKey.(*rsa.PublicKey), crypto.SHA1, hash.Sum(nil), encryptedSig); err != nil {
   184  		return nil, fmt.Errorf("valexa: 证书无法验证 Body 数据, 错误的是(%s)", err)
   185  	}
   186  	
   187  	
   188  	return &bodyBuf, nil
   189  }
   190  
   191  
   192  
   193  
   194  
   195  
   196  
   197  
   198  
   199  
   200  
   201  
   202  
   203  
   204  
   205  
   206  
   207  
   208  
   209