Files
alterminal/internal/jwk/signingkey_test.go
T

176 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package jwk
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"math/big"
"strings"
"testing"
"time"
)
func TestNewSigningKey(t *testing.T) {
k, err := NewSigningKey()
if err != nil {
t.Fatal("NewSigningKey: ", err)
}
if len(k.Kid) != 43 {
t.Errorf("Kid 應為 32 bytes SHA-256 的無填充 base64url(43 字元),得到 %d 字元", len(k.Kid))
}
if k.Algorithm != AlgRS256 {
t.Errorf("Algorithm = %q, want %q", k.Algorithm, AlgRS256)
}
if !k.Active() {
t.Error("新產生的金鑰應為使用中(RetiredAt 為 nil)")
}
priv, err := k.PrivateKey()
if err != nil {
t.Fatal("PrivateKey: ", err)
}
if priv.N.BitLen() != rsaKeyBits {
t.Errorf("金鑰長度 = %d bits, want %d", priv.N.BitLen(), rsaKeyBits)
}
if err := priv.Validate(); err != nil {
t.Error("產生的私鑰未通過自檢: ", err)
}
want, err := thumbprint(&priv.PublicKey)
if err != nil {
t.Fatal("thumbprint: ", err)
}
if k.Kid != want {
t.Errorf("Kid = %q, want 公鑰 thumbprint %q", k.Kid, want)
}
}
func TestNewSigningKeyUniqueKid(t *testing.T) {
a, err := NewSigningKey()
if err != nil {
t.Fatal(err)
}
b, err := NewSigningKey()
if err != nil {
t.Fatal(err)
}
if a.Kid == b.Kid {
t.Error("兩把新產生的金鑰不應有相同 Kid")
}
}
// RFC 7638 §3.1 的測試向量(與 RFC 7517 A.1 同一把 RSA 金鑰)。
func TestThumbprintRFC7638Vector(t *testing.T) {
pub := &rsa.PublicKey{
N: mustBigFromBase64URL(t, "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"),
E: 65537,
}
got, err := thumbprint(pub)
if err != nil {
t.Fatal("thumbprint: ", err)
}
if want := "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"; got != want {
t.Errorf("thumbprint = %q, want RFC 7638 §3.1 的 %q", got, want)
}
}
func TestActive(t *testing.T) {
k := &SigningKey{}
if !k.Active() {
t.Error("RetiredAt 為 nil 時應為使用中")
}
now := time.Now()
k.RetiredAt = &now
if k.Active() {
t.Error("RetiredAt 已設定時不應為使用中")
}
}
func TestPublicJWK(t *testing.T) {
k, err := NewSigningKey()
if err != nil {
t.Fatal(err)
}
j, err := k.PublicJWK()
if err != nil {
t.Fatal("PublicJWK: ", err)
}
priv, err := k.PrivateKey()
if err != nil {
t.Fatal(err)
}
if j.Kty != "RSA" || j.Use != "sig" || j.Alg != "RS256" {
t.Errorf("JWK 參數 = kty:%q use:%q alg:%q", j.Kty, j.Use, j.Alg)
}
if j.Kid != k.Kid {
t.Errorf("JWK.Kid = %q, want %q", j.Kid, k.Kid)
}
if j.E != "AQAB" {
t.Errorf("E = %q, want AQAB(65537 的 base64url)", j.E)
}
if mustBigFromBase64URL(t, j.N).Cmp(priv.N) != 0 {
t.Error("N 應等於私鑰的 modulus")
}
}
func TestPublicJWKJSONShape(t *testing.T) {
k, err := NewSigningKey()
if err != nil {
t.Fatal(err)
}
j, err := k.PublicJWK()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(JWKS{Keys: []JWK{*j}})
if err != nil {
t.Fatal(err)
}
s := string(b)
if !strings.HasPrefix(s, `{"keys":[{`) || !strings.HasSuffix(s, `}]}`) {
t.Errorf(`JWKS 應為 {"keys":[…]} 形式,得到 %s`, s)
}
var m struct {
Keys []map[string]any `json:"keys"`
}
if err := json.Unmarshal(b, &m); err != nil {
t.Fatal(err)
}
got := m.Keys[0]
want := []string{"alg", "e", "kid", "kty", "n", "use"}
if len(got) != len(want) {
t.Errorf("JWK 應恰含參數 %v,得到 %v", want, got)
}
for _, f := range want {
if _, ok := got[f]; !ok {
t.Errorf("JWK 缺少參數 %q", f)
}
}
// 私有參數絕不得出現
for _, p := range []string{`"d":`, `"p":`, `"q":`, `"dp":`, `"dq":`, `"qi":`} {
if strings.Contains(s, p) {
t.Errorf("JWKS 不應含私有參數 %s", p)
}
}
}
func TestPrivateKeyMalformedPEM(t *testing.T) {
for _, pemStr := range []string{
"",
"not a pem",
"-----BEGIN PRIVATE KEY-----\nYm9ndXMK\n-----END PRIVATE KEY-----",
} {
k := &SigningKey{PrivateKeyPEM: pemStr}
if _, err := k.PrivateKey(); err == nil {
t.Errorf("格式無效的 PEM %q 不應解析成功", pemStr)
}
}
}
func mustBigFromBase64URL(t *testing.T, s string) *big.Int {
t.Helper()
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
t.Fatalf("decode base64url %q: %v", s, err)
}
return new(big.Int).SetBytes(b)
}