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