Skip to content

Commit 3fff766

Browse files
committed
feature: support ec/refactor cmd
Signed-off-by: xiexianbin <me@xiexianbin.cn>
1 parent e3f360e commit 3fff766

File tree

18 files changed

+1030
-598
lines changed

18 files changed

+1030
-598
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
1616
.DS_Store
17-
bin/
1817
.history/
1918
.idea/
2019
.lh/
20+
bin/
21+
vendor
2122
x-ca

Makefile

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,40 +69,40 @@ clean: ## Run clean bin files
6969

7070
.PHONY: build
7171
build: ## Build for current os
72-
${SUB_BUILD_CMD} -o bin/$(BINARY_NAME)
72+
${SUB_BUILD_CMD} -o bin/$(BINARY_NAME) ./cmd/...
7373

7474
.PHONY: linux-amd64
7575
linux-amd64: ## Build linux amd64
76-
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@
76+
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@ ./cmd/...
7777

7878
.PHONY: linux-arm64
7979
linux-arm64: ## Build linux arm64
80-
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@
80+
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@ ./cmd/...
8181

8282
.PHONY: linux-ppc64le
8383
linux-ppc64le: ## Build linux ppc64le
84-
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@
84+
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@ ./cmd/...
8585

8686
.PHONY: linux-s390x
8787
linux-s390x: ## Build linux s390x
88-
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@
88+
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@ ./cmd/...
8989

9090
.PHONY: darwin-amd64
9191
darwin-amd64: ## Build darwin amd64
92-
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@
92+
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@ ./cmd/...
9393

9494
.PHONY: darwin-arm64
9595
darwin-arm64: ## Build darwin arm64
96-
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@
96+
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@ ./cmd/...
9797

9898
.PHONY: windows-amd64
9999
windows-amd64: ## Build windows amd64
100-
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@.exe
100+
CGO_ENABLED=0 ${GOARGS} ${SUB_BUILD_CMD} -o bin/${BINARY_NAME}-$@.exe ./cmd/...
101101

102-
.PHONY: docker-build
103-
docker-build: test ## Build docker image
104-
docker build -t ${IMG} .
102+
# .PHONY: docker-build
103+
# docker-build: test ## Build docker image
104+
# docker build -t ${IMG} .
105105

106-
.PHONY: docker-push
107-
docker-push: ## Push docker image
108-
docker push ${IMG}
106+
# .PHONY: docker-push
107+
# docker-push: ## Push docker image
108+
# docker push ${IMG}

README.md

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mv xca /usr/local/bin/
2121
```
2222
$ xca --help
2323
Create Root CA and TLS CA:
24-
xca -create-ca true \
24+
xca -create-ca \
2525
-root-cert x-ca/ca/root-ca.crt \
2626
-root-key x-ca/ca/root-ca/private/root-ca.key \
2727
-tls-cert x-ca/ca/tls-ca.crt \
@@ -67,10 +67,37 @@ Source Code:
6767

6868
## Usage Demo
6969

70-
- create ca
70+
### create EC CA
71+
72+
You can specify the key type (`-key-type`) and curve (`-curve`) to create an EC root CA and TLS CA:
73+
74+
```
75+
./xca -create-ca \
76+
-root-cert x-ca/ca/root-ca.crt \
77+
-root-key x-ca/ca/root-ca/private/root-ca.key \
78+
-tls-cert x-ca/ca/tls-ca.crt \
79+
-tls-key x-ca/ca/tls-ca/private/tls-ca.key \
80+
-tls-chain x-ca/ca/tls-ca-chain.pem \
81+
-key-type ec \
82+
-curve P256
83+
```
84+
85+
To sign a certificate with an EC key:
86+
87+
```
88+
./xca -cn example.com \
89+
--domains "example.com" \
90+
-tls-cert x-ca/ca/tls-ca.crt \
91+
-tls-key x-ca/ca/tls-ca/private/tls-ca.key \
92+
-tls-chain x-ca/ca/tls-ca-chain.pem \
93+
-key-type ec \
94+
-curve P256
95+
```
96+
97+
### create RSA CA
7198

7299
```
73-
xca -create-ca true \
100+
xca -create-ca \
74101
-root-cert x-ca/ca/root-ca.crt \
75102
-root-key x-ca/ca/root-ca/private/root-ca.key \
76103
-tls-cert x-ca/ca/tls-ca.crt \
@@ -117,3 +144,35 @@ Use `openssl rsa -in root-ca.key -des3` change cipher
117144
## Ref
118145

119146
- [基于OpenSSL签署根CA、二级CA](https://www.xiexianbin.cn/s/ca/)
147+
148+
```
149+
go.mod - Added cobra dependency
150+
ca/baseca.go - Common CA functionality
151+
ca/common.go - Shared utilities
152+
cmd/create.go - create-ca command
153+
cmd/sign.go - sign command
154+
cmd/root.go - root cobra command
155+
cmd/xca.go - main entry point (refactored)
156+
```
157+
158+
## Usage Examples
159+
160+
```
161+
162+
go build -o bin/xca ./cmd/...
163+
164+
# Create CA certificates
165+
xca create-ca --key-type ec --curve P256
166+
167+
# Sign domain certificate
168+
xca sign example.com --domains "example.com,www.example.com"
169+
170+
# Sign IP certificate
171+
xca sign 192.168.1.1 --ips "192.168.1.1"
172+
173+
# Get help
174+
xca --help
175+
xca create-ca --help
176+
xca sign --help
177+
178+
```

ca/base.go

Lines changed: 173 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,180 @@ limitations under the License.
1313

1414
package ca
1515

16+
import (
17+
"crypto/ecdsa"
18+
"crypto/elliptic"
19+
"crypto/rand"
20+
"crypto/rsa"
21+
"crypto/x509"
22+
"encoding/pem"
23+
"fmt"
24+
"os"
25+
"path"
26+
"strings"
27+
)
28+
1629
type CA interface {
17-
CreateKey() error
30+
GenerateKey() error
1831
CreateCert() error
1932
Write(keyPath, certPath, chainPath string) error
20-
//Load(keyPath, certPath string) (interface{}, error)
33+
//Load(keyPath, certPath string) (any, error)
34+
}
35+
36+
// BaseCA represents common functionality for all CA types
37+
type BaseCA struct {
38+
Key any // *rsa.PrivateKey or *ecdsa.PrivateKey
39+
Cert *x509.Certificate
40+
KeyBits int
41+
Curve string
42+
}
43+
44+
// GenerateKey generates a new private key based on key type
45+
func (b *BaseCA) GenerateKey(keyType string) error {
46+
switch strings.ToLower(keyType) {
47+
case "ec", "ecdsa":
48+
var curve elliptic.Curve
49+
switch b.Curve {
50+
case "P224":
51+
curve = elliptic.P224()
52+
case "P256":
53+
curve = elliptic.P256()
54+
case "P384":
55+
curve = elliptic.P384()
56+
case "P521":
57+
curve = elliptic.P521()
58+
default:
59+
return fmt.Errorf("unsupported curve %s", b.Curve)
60+
}
61+
key, err := ecdsa.GenerateKey(curve, rand.Reader)
62+
if err != nil {
63+
return err
64+
}
65+
b.Key = key
66+
case "rsa":
67+
key, err := rsa.GenerateKey(rand.Reader, b.KeyBits)
68+
if err != nil {
69+
return err
70+
}
71+
b.Key = key
72+
default:
73+
return fmt.Errorf("unsupported key type %s", keyType)
74+
}
75+
return nil
76+
}
77+
78+
// GetPublicKey extracts the public key from the private key
79+
func (b *BaseCA) GetPublicKey() (any, error) {
80+
switch k := b.Key.(type) {
81+
case *rsa.PrivateKey:
82+
return k.Public(), nil
83+
case *ecdsa.PrivateKey:
84+
return k.Public(), nil
85+
default:
86+
return nil, fmt.Errorf("unsupported key type")
87+
}
88+
}
89+
90+
// WriteKey writes the private key to a PEM file
91+
func (b *BaseCA) WriteKey(keyPath string) error {
92+
// Create directory if it doesn't exist
93+
err := os.MkdirAll(path.Dir(keyPath), 0700)
94+
if err != nil && !os.IsExist(err) {
95+
return err
96+
}
97+
98+
keyFile, err := os.OpenFile(keyPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
99+
if err != nil {
100+
return err
101+
}
102+
defer keyFile.Close()
103+
104+
var keyType string
105+
var keyBytes []byte
106+
switch k := b.Key.(type) {
107+
case *rsa.PrivateKey:
108+
keyType = "RSA PRIVATE KEY"
109+
keyBytes = x509.MarshalPKCS1PrivateKey(k)
110+
case *ecdsa.PrivateKey:
111+
keyType = "EC PRIVATE KEY"
112+
var err error
113+
keyBytes, err = x509.MarshalECPrivateKey(k)
114+
if err != nil {
115+
return err
116+
}
117+
default:
118+
return fmt.Errorf("unsupported key type")
119+
}
120+
121+
return pem.Encode(keyFile, &pem.Block{
122+
Type: keyType,
123+
Bytes: keyBytes,
124+
})
125+
}
126+
127+
// WriteCert writes the certificate to a PEM file
128+
func (b *BaseCA) WriteCert(certPath string) error {
129+
// Create directory if it doesn't exist
130+
err := os.MkdirAll(path.Dir(certPath), 0700)
131+
if err != nil && !os.IsExist(err) {
132+
return err
133+
}
134+
135+
certFile, err := os.OpenFile(certPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
136+
if err != nil {
137+
return err
138+
}
139+
defer certFile.Close()
140+
141+
return pem.Encode(certFile, &pem.Block{
142+
Type: "CERTIFICATE",
143+
Bytes: b.Cert.Raw,
144+
})
145+
}
146+
147+
// LoadKey loads a private key from a PEM file
148+
func (b *BaseCA) LoadKey(keyPath string) error {
149+
keyBytes, err := os.ReadFile(keyPath)
150+
if err != nil {
151+
return err
152+
}
153+
154+
keyBlock, _ := pem.Decode(keyBytes)
155+
if keyBlock == nil {
156+
return fmt.Errorf("decode key is nil")
157+
}
158+
159+
// Check if encrypted
160+
isEncrypted := len(keyBlock.Headers) > 0 && keyBlock.Headers["Proc-Type"] == "4,ENCRYPTED"
161+
if isEncrypted {
162+
return fmt.Errorf("encrypted PEM blocks are not supported - please decrypt your key first, using: openssl rsa -in encrypted.key -out decrypted.key")
163+
}
164+
165+
switch keyBlock.Type {
166+
case "RSA PRIVATE KEY":
167+
b.Key, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
168+
case "EC PRIVATE KEY":
169+
b.Key, err = x509.ParseECPrivateKey(keyBlock.Bytes)
170+
default:
171+
return fmt.Errorf("unsupported PEM type %s", keyBlock.Type)
172+
}
173+
return err
174+
}
175+
176+
// LoadCert loads a certificate from a PEM file
177+
func (b *BaseCA) LoadCert(certPath string) error {
178+
certBytes, err := os.ReadFile(certPath)
179+
if err != nil {
180+
return err
181+
}
182+
183+
certBlock, _ := pem.Decode(certBytes)
184+
if certBlock == nil {
185+
return fmt.Errorf("decode cert is nil")
186+
} else if certBlock.Type != "CERTIFICATE" {
187+
return fmt.Errorf("unsupported PEM type %s", certBlock.Type)
188+
}
189+
190+
b.Cert, err = x509.ParseCertificate(certBlock.Bytes)
191+
return err
21192
}

0 commit comments

Comments
 (0)