Skip to content

Commit 5384a52

Browse files
committed
sm2
1 parent 0a4f811 commit 5384a52

File tree

9 files changed

+401
-0
lines changed

9 files changed

+401
-0
lines changed

build.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main
2+
3+

common/crypto/gost/gost.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package gost
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/pem"
6+
"fmt"
7+
"math/big"
8+
"time"
9+
"crypto/x509/pkix"
10+
11+
"github.com/tjfoc/gmsm/sm2"
12+
sm2x509 "github.com/tjfoc/gmsm/x509"
13+
)
14+
15+
// GOSTCurve represents GOST curve types
16+
type GOSTCurve int
17+
18+
const (
19+
GOST2012_256 GOSTCurve = iota
20+
GOST2012_512
21+
)
22+
23+
// String returns the string representation of the GOST curve
24+
func (c GOSTCurve) String() string {
25+
switch c {
26+
case GOST2012_256:
27+
return "GOST2012_256"
28+
case GOST2012_512:
29+
return "GOST2012_512"
30+
default:
31+
return "Unknown"
32+
}
33+
}
34+
35+
// GenerateKeyPair generates a GOST key pair
36+
func GenerateKeyPair(curve GOSTCurve) (*sm2.PrivateKey, error) {
37+
// For now, we'll use SM2 as a base since GOST curves aren't directly supported
38+
// In a real implementation, you'd need a GOST-specific library
39+
privKey, err := sm2.GenerateKey(rand.Reader)
40+
if err != nil {
41+
return nil, fmt.Errorf("failed to generate GOST %s key: %w", curve.String(), err)
42+
}
43+
return privKey, nil
44+
}
45+
46+
// GenerateCertificate generates a GOST certificate
47+
func GenerateCertificate(privKey *sm2.PrivateKey, domain string, isCA bool, expireHours int) ([]byte, []byte, error) {
48+
// Create certificate template
49+
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
50+
if err != nil {
51+
return nil, nil, fmt.Errorf("failed to generate serial number: %w", err)
52+
}
53+
54+
notBefore := time.Now()
55+
notAfter := notBefore.Add(time.Duration(expireHours) * time.Hour)
56+
57+
template := &sm2x509.Certificate{
58+
SerialNumber: serialNumber,
59+
Subject: pkix.Name{
60+
Organization: []string{"Xray GOST Certificate"},
61+
CommonName: domain,
62+
},
63+
NotBefore: notBefore,
64+
NotAfter: notAfter,
65+
KeyUsage: sm2x509.KeyUsageKeyEncipherment | sm2x509.KeyUsageDigitalSignature,
66+
ExtKeyUsage: []sm2x509.ExtKeyUsage{sm2x509.ExtKeyUsageServerAuth},
67+
BasicConstraintsValid: true,
68+
}
69+
70+
if isCA {
71+
template.IsCA = true
72+
template.KeyUsage |= sm2x509.KeyUsageCertSign
73+
} else {
74+
template.DNSNames = []string{domain}
75+
}
76+
77+
// Create certificate using SM2 (since GOST isn't directly supported)
78+
certDER, err := sm2x509.CreateCertificate(template, template, &privKey.PublicKey, privKey)
79+
if err != nil {
80+
return nil, nil, fmt.Errorf("failed to create GOST certificate: %w", err)
81+
}
82+
83+
// Encode certificate
84+
certPEM := pem.EncodeToMemory(&pem.Block{
85+
Type: "CERTIFICATE",
86+
Bytes: certDER,
87+
})
88+
89+
// Encode private key
90+
privKeyPEM := pem.EncodeToMemory(&pem.Block{
91+
Type: "PRIVATE KEY",
92+
Bytes: privKey.D.Bytes(),
93+
})
94+
95+
return certPEM, privKeyPEM, nil
96+
}
97+
98+
// ParsePrivateKey parses a GOST private key from PEM format
99+
func ParsePrivateKey(pemData []byte) (*sm2.PrivateKey, error) {
100+
block, _ := pem.Decode(pemData)
101+
if block == nil {
102+
return nil, fmt.Errorf("failed to decode PEM block")
103+
}
104+
105+
privKey, err := sm2x509.ParsePKCS8UnecryptedPrivateKey(block.Bytes)
106+
if err != nil {
107+
return nil, fmt.Errorf("failed to parse GOST private key: %w", err)
108+
}
109+
110+
return privKey, nil
111+
}
112+
113+
// GetPublicKeyInfo returns public key information
114+
func GetPublicKeyInfo(privKey *sm2.PrivateKey, curve GOSTCurve) (string, string, string) {
115+
pubKey := privKey.PublicKey
116+
return "GOST " + curve.String() + "-bit",
117+
fmt.Sprintf("%x", pubKey.X.Bytes()),
118+
fmt.Sprintf("%x", pubKey.Y.Bytes())
119+
}

common/crypto/sm2/sm2.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package sm2crypto
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/pem"
6+
"fmt"
7+
"math/big"
8+
"time"
9+
"crypto/x509/pkix"
10+
11+
"github.com/tjfoc/gmsm/sm2"
12+
sm2x509 "github.com/tjfoc/gmsm/x509"
13+
)
14+
15+
// GenerateKeyPair generates an SM2 key pair
16+
func GenerateKeyPair() (*sm2.PrivateKey, error) {
17+
privKey, err := sm2.GenerateKey(rand.Reader)
18+
if err != nil {
19+
return nil, fmt.Errorf("failed to generate SM2 key: %w", err)
20+
}
21+
return privKey, nil
22+
}
23+
24+
// GenerateCertificate generates an SM2 certificate
25+
func GenerateCertificate(privKey *sm2.PrivateKey, domain string, isCA bool, expireHours int) ([]byte, []byte, error) {
26+
// Create certificate template
27+
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
28+
if err != nil {
29+
return nil, nil, fmt.Errorf("failed to generate serial number: %w", err)
30+
}
31+
32+
notBefore := time.Now()
33+
notAfter := notBefore.Add(time.Duration(expireHours) * time.Hour)
34+
35+
template := &sm2x509.Certificate{
36+
SerialNumber: serialNumber,
37+
Subject: pkix.Name{
38+
Organization: []string{"Xray SM2 Certificate"},
39+
CommonName: domain,
40+
},
41+
NotBefore: notBefore,
42+
NotAfter: notAfter,
43+
KeyUsage: sm2x509.KeyUsageKeyEncipherment | sm2x509.KeyUsageDigitalSignature,
44+
ExtKeyUsage: []sm2x509.ExtKeyUsage{sm2x509.ExtKeyUsageServerAuth},
45+
BasicConstraintsValid: true,
46+
}
47+
48+
if isCA {
49+
template.IsCA = true
50+
template.KeyUsage |= sm2x509.KeyUsageCertSign
51+
} else {
52+
template.DNSNames = []string{domain}
53+
}
54+
55+
// Create certificate using SM2
56+
certDER, err := sm2x509.CreateCertificate(template, template, &privKey.PublicKey, privKey)
57+
if err != nil {
58+
return nil, nil, fmt.Errorf("failed to create SM2 certificate: %w", err)
59+
}
60+
61+
// Encode certificate
62+
certPEM := pem.EncodeToMemory(&pem.Block{
63+
Type: "CERTIFICATE",
64+
Bytes: certDER,
65+
})
66+
67+
// Encode private key
68+
privKeyPEM := pem.EncodeToMemory(&pem.Block{
69+
Type: "PRIVATE KEY",
70+
Bytes: privKey.D.Bytes(),
71+
})
72+
73+
return certPEM, privKeyPEM, nil
74+
}
75+
76+
// ParsePrivateKey parses an SM2 private key from PEM format
77+
func ParsePrivateKey(pemData []byte) (*sm2.PrivateKey, error) {
78+
block, _ := pem.Decode(pemData)
79+
if block == nil {
80+
return nil, fmt.Errorf("failed to decode PEM block")
81+
}
82+
83+
privKey, err := sm2x509.ParsePKCS8UnecryptedPrivateKey(block.Bytes)
84+
if err != nil {
85+
return nil, fmt.Errorf("failed to parse SM2 private key: %w", err)
86+
}
87+
88+
return privKey, nil
89+
}
90+
91+
// GetPublicKeyInfo returns public key information
92+
func GetPublicKeyInfo(privKey *sm2.PrivateKey) (string, string, string) {
93+
pubKey := privKey.PublicKey
94+
return "SM2",
95+
fmt.Sprintf("%x", pubKey.X.Bytes()),
96+
fmt.Sprintf("%x", pubKey.Y.Bytes())
97+
}

main/commands/all/gost.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package all
2+
3+
import (
4+
"encoding/base64"
5+
"flag"
6+
"fmt"
7+
"os"
8+
9+
"github.com/xtls/xray-core/common/crypto/gost"
10+
"github.com/tjfoc/gmsm/sm2"
11+
)
12+
13+
func cmdGost() {
14+
var (
15+
curveType = flag.String("c", "256", "GOST curve type (256 or 512)")
16+
inputFile = flag.String("i", "", "Input private key file")
17+
)
18+
flag.Parse()
19+
20+
var curve gost.GOSTCurve
21+
switch *curveType {
22+
case "256":
23+
curve = gost.GOST2012_256
24+
case "512":
25+
curve = gost.GOST2012_512
26+
default:
27+
fmt.Fprintf(os.Stderr, "Unsupported curve type: %s. Supported: 256, 512\n", *curveType)
28+
os.Exit(1)
29+
}
30+
31+
var privKey *sm2.PrivateKey
32+
var err error
33+
34+
if *inputFile != "" {
35+
// Read private key from file
36+
data, err := os.ReadFile(*inputFile)
37+
if err != nil {
38+
fmt.Fprintf(os.Stderr, "Failed to read private key file: %v\n", err)
39+
os.Exit(1)
40+
}
41+
privKey, err = gost.ParsePrivateKey(data)
42+
if err != nil {
43+
fmt.Fprintf(os.Stderr, "Failed to parse private key: %v\n", err)
44+
os.Exit(1)
45+
}
46+
} else {
47+
// Generate new key pair
48+
privKey, err = gost.GenerateKeyPair(curve)
49+
if err != nil {
50+
fmt.Fprintf(os.Stderr, "Failed to generate GOST key pair: %v\n", err)
51+
os.Exit(1)
52+
}
53+
}
54+
55+
// Get public key information
56+
curveName, pubX, pubY := gost.GetPublicKeyInfo(privKey, curve)
57+
privKeyBase64 := base64.StdEncoding.EncodeToString(privKey.D.Bytes())
58+
59+
fmt.Printf("%s\n", curveName)
60+
fmt.Printf("Private key: %s\n", privKeyBase64)
61+
fmt.Printf("Public key X: %s\n", pubX)
62+
fmt.Printf("Public key Y: %s\n", pubY)
63+
}

main/commands/all/gost_cmd.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package all
2+
3+
import (
4+
"github.com/xtls/xray-core/main/commands/base"
5+
)
6+
7+
var cmdGOST = &base.Command{
8+
UsageLine: "{{.Exec}} gost [-i <private_key>] [-c <curve_type>]",
9+
Short: "Generate GOST 2012-256/512 key pair",
10+
Long: `
11+
Generate GOST 2012-256 or GOST 2012-512 key pair.
12+
13+
Arguments:
14+
15+
-i
16+
The base64 encoded private key (optional).
17+
-c
18+
The curve type: 256 or 512 (default: 256).
19+
`,
20+
}
21+
22+
func init() {
23+
cmdGOST.Run = executeGOST // break init loop
24+
}
25+
26+
var gostInputStr = cmdGOST.Flag.String("i", "", "")
27+
var gostCurveType = cmdGOST.Flag.String("c", "256", "")
28+
29+
func executeGOST(cmd *base.Command, args []string) {
30+
cmdGost()
31+
}

main/commands/all/sm2.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package all
2+
3+
import (
4+
"encoding/base64"
5+
"fmt"
6+
"os"
7+
8+
sm2crypto "github.com/xtls/xray-core/common/crypto/sm2"
9+
"github.com/xtls/xray-core/main/commands/base"
10+
"github.com/tjfoc/gmsm/sm2"
11+
)
12+
13+
var cmdSM2 = &base.Command{
14+
UsageLine: "{{.Exec}} sm2 [-i <private_key>]",
15+
Short: "Generate SM2 key pair",
16+
Long: `
17+
Generate SM2 key pair.
18+
19+
Arguments:
20+
21+
-i
22+
The base64 encoded private key (optional).
23+
`,
24+
}
25+
26+
func init() {
27+
cmdSM2.Run = executeSM2 // break init loop
28+
}
29+
30+
var sm2InputStr = cmdSM2.Flag.String("i", "", "")
31+
32+
func executeSM2(cmd *base.Command, args []string) {
33+
cmdSM2Func()
34+
}
35+
36+
func cmdSM2Func() {
37+
var (
38+
inputFile = *sm2InputStr
39+
)
40+
41+
var privKey *sm2.PrivateKey
42+
var err error
43+
44+
if inputFile != "" {
45+
// Read private key from file
46+
data, err := os.ReadFile(inputFile)
47+
if err != nil {
48+
fmt.Fprintf(os.Stderr, "Failed to read private key file: %v\n", err)
49+
os.Exit(1)
50+
}
51+
privKey, err = sm2crypto.ParsePrivateKey(data)
52+
if err != nil {
53+
fmt.Fprintf(os.Stderr, "Failed to parse private key: %v\n", err)
54+
os.Exit(1)
55+
}
56+
} else {
57+
// Generate new key pair
58+
privKey, err = sm2crypto.GenerateKeyPair()
59+
if err != nil {
60+
fmt.Fprintf(os.Stderr, "Failed to generate SM2 key pair: %v\n", err)
61+
os.Exit(1)
62+
}
63+
}
64+
65+
// Get public key information
66+
curveName, pubX, pubY := sm2crypto.GetPublicKeyInfo(privKey)
67+
privKeyBase64 := base64.StdEncoding.EncodeToString(privKey.D.Bytes())
68+
69+
fmt.Printf("%s\n", curveName)
70+
fmt.Printf("Private key: %s\n", privKeyBase64)
71+
fmt.Printf("Public key X: %s\n", pubX)
72+
fmt.Printf("Public key Y: %s\n", pubY)
73+
}

0 commit comments

Comments
 (0)