2022年7月

安全性警告

由于使用STM32F103C8T6进行软实现,没有使用HSM芯片,故存储在EEPROM中的密钥可能会被调试读出,故存在安全风险,只适合把玩,不适合日常使用。

也存在随机数生成不够随机,侧信道攻击(似乎普遍都有这个问题,不过也不是什么事儿)等安全风险。

前言

没有一个硬件密钥是没办法进行相关开发的,而我暂时没有这个又想把玩一下,所以找了下我吃灰的开发板,找到了一块蓝色长条的STM32F103C8T6开发板,检索一番后最终到了这个开源方案,这个方案只支持U2F验证。

https://github.com/gl-sergei/u2f-token

0719213358.jpg

编译&烧写

由于仓库提供编译好的固件(BLUE_PILL)会使用复位键作为物理验证按钮,所以我们需要修改一下,在lssues找到了一个修改版本,验证时只需要将PA10下拉就行。

本文会省略一些步骤,详情请见仓库README.md

git clone https://github.com/Qwertylex/u2f-token

部署交叉编译工具链:

# 原 PPA 老旧已经失效,故我们需要前往 ARM 网站下载
# 前往 https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/downloads 下载
wget https://developer.arm.com/-/media/Files/downloads/gnu/11.2-2022.02/binrel/gcc-arm-11.2-2022.02-x86_64-arm-none-eabi.tar.xz
tar xvf gcc-arm-11.2-2022.02-x86_64-arm-none-eabi.tar.xz -C /usr/share
ln -s /usr/share/gcc-arm-11.2-2022.02-x86_64-arm-none-eabi/bin/* /usr/bin/

编译:

# 注意:
# 我们需要将 Makefile 中的 python 替换为 python3
cd u2f-token
git submodule update --init
cd src
make TARGET=BLUE_PILL_PA10BUTTON
# 编译出的固件在 build/u2f.bin

烧写:

使用 STM32 ST-LINK Utility 通过 ST-LINK V2 进行烧写。
设置跳线:
BOOT0 SET 0
BOOT1 SET 1

19220213.png

识别:

19221309.png

初始化

cd ~/u2f-token
pip3 install -r requirements.txt --user
cd src/cert
python3 certtool init

19220937.png

测试

前往https://demo.yubico.com/webauthn-technical/registration进行测试即可。

19221106.png

前言

本文主要介绍Golang Gin框架中全局处理错误的方法,水一下文章。

使用嵌套

就是在路由函数外面套层壳,侵入性大,不推荐这种处理方式。

type handle func(c *gin.Context) error

func errorHandle(f handle) func(c *gin.Context) {
    return func(c *gin.Context) {
        err := f(c)
        if err != nil {
            c.JSON(http.StatusInternalServerError, err.Error())
        }
    }
}

使用:

// 套在原路由函数,有多少个套多少次
router.GET("/b", errorHandle(func(c *gin.Context) error {
    return fmt.Errorf("1145141919810")
}))

使用panicrecover

Gin带有一个gin.Recovery中间件,用于捕获panic,说明我们可以通过中间件的方式捕获错误,而且panicrecover是原生的,非常方便,但只能捕获同一协程下的错误。

自定义错误接口:

package custom

var ErrorMessage map[int]string

type RequestError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Stack   string `json:"stack,omitempty"`
}

func (err *RequestError) Error() string {
    return err.Message
}

func NewRequestError(code int, err ...error) *RequestError {
    e := new(RequestError)
    e.Code = code
    if v, ok := ErrorMessage[code]; ok == true {
        e.Message = v
    } else {
        e.Message = "server error"
    }
    // runtime.Stack()
    if len(err) >= 1 {
        for i := range err {
            e.Stack += err[i].Error()
            e.Stack += "\n"
        }
    }
    return e
}

func init() {
    ErrorMessage = make(map[int]string)
    // 以此类推定义错误信息
    ErrorMessage[401] = "Unauthorized"
}

自定义中间件:

package custom

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                // 类型断言,因为 panic 是个 any ,什么都可以丢
                if v, ok := err.(*RequestError); ok == true {
                    c.JSON(v.Code, v)
                } else {
                    c.String(http.StatusInternalServerError, "Internal Server Error")
                }
                c.Abort()
            }
        }()
        // 传递下一棒
        c.Next()
    }
}

使用:

router.Use(custom.ErrorHandler())

router.GET("/a", func(c *gin.Context) {
    // 抛个错误
    panic(custom.NewRequestError(401))
    c.String(http.StatusOK, "hello")
})

15154424.png

前言

本文不是Lego使用教程。

由于SSL证书一年也就申请给几次,所以一般我都是手动去配置申请,上次介绍了Certbot提供acme模块用法,本文将介绍lego这个ACME客户端所提供的的库的用法。基本上重新造了个轮子,没有去用内部具体的接口。

实现

package main

import (
    "crypto"
    "crypto/x509"
    "encoding/pem"
    "github.com/go-acme/lego/v4/certcrypto"
    "github.com/go-acme/lego/v4/certificate"
    "github.com/go-acme/lego/v4/challenge/dns01"
    "github.com/go-acme/lego/v4/lego"
    "github.com/go-acme/lego/v4/log"
    "github.com/go-acme/lego/v4/registration"
    "io/ioutil"
    "time"
)

// 参见: https://pkg.go.dev/github.com/go-acme/lego/[email protected]/registration#User
type AcmeUser struct {
    Email        string
    Registration *registration.Resource
    key          crypto.PrivateKey
}

func (u *AcmeUser) GetEmail() string {
    return u.Email
}

func (u *AcmeUser) GetRegistration() *registration.Resource {
    return u.Registration
}
func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey {
    return u.key
}

// 实现 Provider 接口
// 参见: https://pkg.go.dev/github.com/go-acme/lego/v4/[email protected]#Provider
// lego本身提供一些DNS服务商的接口,但我一般都是手动申请
type plainDnsProvider struct{}

func (p *plainDnsProvider) Present(domain, token, keyAuth string) error {
    log.Infof("domain: _acme-challenge.%s token: %s", domain, token)
    return nil
}

func (p *plainDnsProvider) CleanUp(domain, token, keyAuth string) error {
    return nil
}

func main() {
    // 本地ACME测试用
    // httpClient := &http.Client{
    //     Transport: &http.Transport{
    //         TLSClientConfig: &tls.Config{InsecureSkipVerify: true
    //     },
    // }}

    key, err := ioutil.ReadFile("account_private.key")
    if err != nil {
        panic(err)
    }
    block, _ := pem.Decode(key)
    priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        panic(err)
    }

    // 加载账户信息
    user := AcmeUser{
        Email: "[email protected]",
        key:   priKey,
    }
    config := lego.NewConfig(&user)
    // Pebble 默认地址
    // config.CADirURL = "https://127.0.0.1:14000/dir"
    // config.CADirURL = "https://api.buypass.com/acme/directory"
    config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
    config.UserAgent = "acm_go/0.0.1"
    // 秘钥类型
    config.Certificate.KeyType = certcrypto.RSA2048
    // 本地测试用
    // config.HTTPClient = httpClient

    // 新建客户端
    client, err := lego.NewClient(config)
    if err != nil {
        panic(err)
    }

    // 账户注册
    reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
    // 使用秘钥查找账户
    // reg, err := client.Registration.ResolveAccountByKey()
    if err != nil {
        panic(err)
    }
    user.Registration = reg

    // 生成证书请求
    request := certificate.ObtainRequest{
        Domains: []string{"yajyusenpai.coat.co.jp"},
        // 证书链
        Bundle: true,
    }

    dnsProvider := plainDnsProvider{}
    // dns.NewDNSChallengeProviderByName(provider_name)
    // dnspodConfig := dnspod.Config{config...}
    // dnspodProvider := dnspod.NewDNSProviderConfig(&dnspodConfig)

    // 以DNS方式验证
    err = client.Challenge.SetDNS01Provider(&dnsProvider, dns01.AddDNSTimeout(6*time.Minute))
    if err != nil {
        panic(err)
    }

    // 自动轮询
    certificates, err := client.Certificate.Obtain(request)
    if err != nil {
        panic(err)
    }

    // 导出
    _ = ioutil.WriteFile("certificate.crt", certificates.Certificate, 0755)
    _ = ioutil.WriteFile("certificate_private.key", certificates.PrivateKey, 0755)
}

220707063204.jpg