Created
August 14, 2026 21:11
-
-
Save lidaobing/143884eab3393cfa6bdf0e9d56f161d8 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| package main | |
| import ( | |
| "context" | |
| "crypto" | |
| "crypto/rand" | |
| "crypto/x509" | |
| "crypto/x509/pkix" | |
| "encoding/asn1" | |
| "encoding/pem" | |
| "fmt" | |
| "io" | |
| "log" | |
| "os" | |
| kms "cloud.google.com/go/kms/apiv1" | |
| "github.com/pborman/getopt/v2" | |
| kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" | |
| ) | |
| // BasicConstraintsExtension 对应 ASN.1 BasicConstraints 结构 | |
| type BasicConstraintsExtension struct { | |
| IsCA bool `asn1:"optional"` | |
| MaxPathLen int `asn1:"optional,default:-1"` | |
| } | |
| // KMSSigner 实现了 crypto.Signer 接口 | |
| type KMSSigner struct { | |
| client *kms.KeyManagementClient | |
| keyVersion string | |
| publicKey crypto.PublicKey | |
| } | |
| func NewKMSSigner(ctx context.Context, client *kms.KeyManagementClient, keyVersion string) (*KMSSigner, error) { | |
| req := &kmspb.GetPublicKeyRequest{Name: keyVersion} | |
| response, err := client.GetPublicKey(ctx, req) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to get public key from KMS: %w", err) | |
| } | |
| fmt.Println(response.Pem) | |
| block, _ := pem.Decode([]byte(response.Pem)) | |
| if block == nil { | |
| return nil, fmt.Errorf("failed to parse PEM block") | |
| } | |
| pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to parse PKIX public key: %w", err) | |
| } | |
| return &KMSSigner{ | |
| client: client, | |
| keyVersion: keyVersion, | |
| publicKey: pubKey, | |
| }, nil | |
| } | |
| func (s *KMSSigner) Public() crypto.PublicKey { | |
| return s.publicKey | |
| } | |
| // Sign 实现 crypto.Signer 接口 | |
| func (s *KMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { | |
| ctx := context.Background() | |
| // Ed25519 直接传入原始数据进行签名 | |
| req := &kmspb.AsymmetricSignRequest{ | |
| Name: s.keyVersion, | |
| Data: digest, | |
| } | |
| result, err := s.client.AsymmetricSign(ctx, req) | |
| if err != nil { | |
| return nil, fmt.Errorf("KMS AsymmetricSign call failed: %w", err) | |
| } | |
| return result.Signature, nil | |
| } | |
| func main() { | |
| var ( | |
| help bool | |
| key string | |
| output string = "1.csr" // 默认值 | |
| cn string = "CN1" | |
| ) | |
| // 注册参数:变量指针, 短选项(char), 长选项(string), 描述 | |
| getopt.FlagLong(&help, "help", 'h', "Show help message") | |
| getopt.FlagLong(&key, "key", 'k', "Key URL, sample projects/YOUR_PROJECT/locations/global/keyRings/YOUR_KEYRING/cryptoKeys/YOUR_KEY/cryptoKeyVersions/1") | |
| getopt.FlagLong(&output, "output", 'o', "output CSR file path") | |
| getopt.FlagLong(&cn, "cn", 0, "Cert Subject CN") | |
| // 解析命令行参数 | |
| getopt.Parse() | |
| // 处理帮助请求 | |
| if help { | |
| getopt.Usage() | |
| os.Exit(0) | |
| } | |
| if key == "" { | |
| log.Printf("key is required") | |
| getopt.Usage() | |
| os.Exit(1) | |
| } | |
| ctx := context.Background() | |
| kmsClient, err := kms.NewKeyManagementClient(ctx) | |
| if err != nil { | |
| log.Fatalf("Failed to create KMS client: %v", err) | |
| } | |
| defer kmsClient.Close() | |
| signer, err := NewKMSSigner(ctx, kmsClient, key) | |
| if err != nil { | |
| log.Fatalf("Failed to create KMS Signer: %v", err) | |
| } | |
| // 1. 手动序列化 Basic Constraints (CA: TRUE) | |
| // OID 2.5.29.19 即是 id-ce-basicConstraints | |
| basicConstraintsVal, err := asn1.Marshal(BasicConstraintsExtension{ | |
| IsCA: true, | |
| MaxPathLen: 0, // 限制该 Sub-CA 不能再签发下一级 Intermediate CA(根据需求调整,不限制可设为 -1) | |
| }) | |
| if err != nil { | |
| log.Fatalf("Failed to marshal basic constraints: %v", err) | |
| } | |
| // 2. 定义包含 CA 扩展属性的 CSR Template | |
| csrTemplate := x509.CertificateRequest{ | |
| Subject: pkix.Name{ | |
| CommonName: cn, | |
| }, | |
| ExtraExtensions: []pkix.Extension{ | |
| { | |
| Id: asn1.ObjectIdentifier{2, 5, 29, 19}, // OID for BasicConstraints | |
| Critical: true, // CA 扩展必须设为 Critical | |
| Value: basicConstraintsVal, | |
| }, | |
| { | |
| Id: asn1.ObjectIdentifier{2, 5, 29, 15}, // OID for KeyUsage | |
| Critical: true, | |
| // KeyCertSign (4) | CRLSign (2) => 0x06 | |
| Value: []byte{0x03, 0x02, 0x01, 0x06}, | |
| }, | |
| }, | |
| } | |
| // 3. 调用 x509 库发起请求并通过 KMS 签名生成 CSR | |
| csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, signer) | |
| if err != nil { | |
| log.Fatalf("Failed to create CSR: %v", err) | |
| } | |
| // 4. 导出 PEM | |
| csrPem := pem.EncodeToMemory(&pem.Block{ | |
| Type: "CERTIFICATE REQUEST", | |
| Bytes: csrBytes, | |
| }) | |
| fmt.Println("Successfully generated SubCA CSR with CA:TRUE using Cloud KMS Ed25519 Key:") | |
| fmt.Println(string(csrPem)) | |
| _ = os.WriteFile(output, csrPem, 0644) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment