diff --git a/Makefile b/Makefile index 804b915b..7e0fa8cf 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,7 @@ generate-version-and-build: @$(MAKE) agentd @$(MAKE) acd @$(MAKE) serverd + @$(MAKE) kgc @$(MAKE) agentsdk @$(MAKE) devicesdk @$(MAKE) plugins @@ -63,6 +64,14 @@ serverd: mkdir -p ./release/nhp-server/etc cp ./server/main/etc/*.toml ./release/nhp-server/etc/ +kgc: + @echo "$(COLOUR_BLUE)[KGC] Building KGC module... $(END_COLOUR)" + mkdir -p ./release/kgc/etc + @cd kgc/main && go build -trimpath -ldflags ${LD_FLAGS} -v -o ../../release/kgc/kgc ./main.go + cp ./kgc/main/etc/*.toml ./release/kgc/etc/ 2>/dev/null || true + @echo "$(COLOUR_GREEN)[KGC] Build completed!$(END_COLOUR)" + + agentsdk: ifeq ($(OS_NAME), linux) go build -a -trimpath -buildmode=c-shared -ldflags ${LD_FLAGS} -v -o ./release/nhp-agent/nhp-agent.so ./agent/main/main.go ./agent/main/export.go diff --git a/kgc/kgc.go b/kgc/kgc.go new file mode 100644 index 00000000..22d7ced2 --- /dev/null +++ b/kgc/kgc.go @@ -0,0 +1,194 @@ +package kgc + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "math/big" + "github.com/emmansun/gmsm/sm2" + "github.com/emmansun/gmsm/sm3" + "github.com/BurntSushi/toml" + "os" + "path/filepath" + "github.com/OpenNHP/opennhp/kgc/user" +) + +var( + id = user.ID + curve = sm2.P256() + N = curve.Params().N + Gx = curve.Params().Gx + Gy = curve.Params().Gy + IdA = []byte(id) + EntlA = len(IdA) * 8 + Ms *big.Int + PpubX *big.Int + PpubY *big.Int + curveParams CurveParams + A, B *big.Int + WAx, WAy, W *big.Int + HA []byte + L *big.Int + TA *big.Int +) + +// A structure for storing configuration +type CurveParams struct { + A string `toml:"a"` + B string `toml:"b"` +} + +//InitConfig loads the configuration and initializes global variables +func InitConfig() error { + // Get the current working directory path + wd, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current directory: %v", err) + } + + // Path to splice TOML files + tomlFilePath := filepath.Join(wd, "kgc", "main", "etc", "Curve.toml") + + // Read and parse TOML files + _, err = toml.DecodeFile(tomlFilePath, &curveParams) + if err != nil { + return fmt.Errorf("error loading TOML file: %v", err) + } + + // Convert a and b from strings in TOML file to big.Int type + A = new(big.Int) + A.SetString(curveParams.A, 16) + B = new(big.Int) + B.SetString(curveParams.B, 16) + return nil +} + +func GetA() *big.Int { + return A +} + +func GetB() *big.Int { + return B +} + +// GenerateMasterKeyPairSM2,Generate the system's master private key ms and master public key Ppub +func GenerateMasterKeyPairSM2() (*big.Int, *big.Int, error) { + curve := sm2.P256() + ms, err := rand.Int(rand.Reader, curve.Params().N) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate system master private key ms: %v", err) + } + if ms.Cmp(big.NewInt(0)) == 0 { + ms, err = rand.Int(rand.Reader, curve.Params().N) + if err != nil { + return nil, nil, fmt.Errorf("regeneration of system master private key ms failed: %v", err) + } + } + Ppubx, Ppuby := curve.ScalarBaseMult(ms.Bytes()) + Ms = ms + PpubX = Ppubx + PpubY = Ppuby + return Ppubx, Ppuby, nil +} + +// GenerateWA,Calculate WA = [w]G + UA +func GenerateWA(UAx, UAy *big.Int) (*big.Int, *big.Int, *big.Int, error) { + curve := sm2.P256() + // Generate a random number w in the range [1, n-1] + w, err := rand.Int(rand.Reader, curve.Params().N) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to generate random number w: %v", err) + } + + // Make sure w is not 0 + if w.Cmp(big.NewInt(0)) == 0 { + w, err = rand.Int(rand.Reader, curve.Params().N) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to regenerate random number w: %v", err) + } + } + Wx, Wy := curve.ScalarBaseMult(w.Bytes()) + wAx, wAy := curve.Add(Wx, Wy, UAx, UAy) + WAx = wAx + WAy = wAy + W = w + return WAx, WAy, w, nil +} + +// Calculate HA = H256(entlA || idA || a || b || xG || yG || xPub || yPub) +func CalculateHA(entlA int, idA []byte, a, b, xG, yG, xPub, yPub *big.Int) ([]byte,error) { + if a == nil || b == nil || xG == nil || yG == nil || xPub == nil || yPub == nil { + return nil, fmt.Errorf("one or more big.Int parameters passed in were nil") + } + entlABytes := make([]byte, 2) + binary.BigEndian.PutUint16(entlABytes, uint16(entlA)) + data := append(entlABytes, idA...) + data = append(data, a.Bytes()...) + data = append(data, b.Bytes()...) + data = append(data, xG.Bytes()...) + data = append(data, yG.Bytes()...) + data = append(data, xPub.Bytes()...) + data = append(data, yPub.Bytes()...) + hash := sm3.New() + hash.Write(data) + ha := hash.Sum(nil) + HA = ha + return HA,nil +} + +// ComputeL l = H256(xWA‖yWA‖HA) mod n +func ComputeL(xWA, yWA *big.Int, HA []byte, n *big.Int) (*big.Int, error) { + xBits := intToBitString(xWA) + yBits := intToBitString(yWA) + hashData := append(xBits, yBits...) + hashData = append(hashData, HA...) + hash := sm3.Sum(hashData) + l := new(big.Int).SetBytes(hash[:]) + l.Mod(l, n) + if l.Cmp(big.NewInt(0)) < 0 { + return nil, fmt.Errorf("the calculated result l is a negative number") + } + k := (n.BitLen() + 7) / 8 + lBytes := intToBytes(l, k) + lInteger := new(big.Int).SetBytes(lBytes) + L = lInteger + return L, nil +} + +// intToBitString +func intToBitString(x *big.Int) []byte { + bitLen := x.BitLen() + byteLen := (bitLen + 7) / 8 + bitString := make([]byte, byteLen) + xBytes := x.Bytes() + copy(bitString[byteLen-len(xBytes):], xBytes) + return bitString +} + +// intToBytes +func intToBytes(x *big.Int, k int) []byte { + m := make([]byte, k) + xBytes := x.Bytes() + copy(m[k-len(xBytes):], xBytes) + return m +} + + +//Calculate tA= w + (l * ms) +func ComputeTA(w, lInteger, ms, n *big.Int) *big.Int { + tA := new(big.Int).Set(w) + lMod := new(big.Int).Mod(lInteger, n) + msMod := new(big.Int).Mod(ms, n) + lMulMs := new(big.Int).Mul(lMod, msMod) + lMulMs.Mod(lMulMs, n) + tA.Add(tA, lMulMs) + tA.Mod(tA, n) + TA = tA + return TA +} + + + + + + diff --git a/kgc/main/etc/Curve.toml b/kgc/main/etc/Curve.toml new file mode 100644 index 00000000..6493e6eb --- /dev/null +++ b/kgc/main/etc/Curve.toml @@ -0,0 +1,3 @@ +[sm2] +a = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF" +b = "28E9FA9E9D9A3E004F2018D6F8C7193A3197D1A9F23A5C24B545C1E3A09F31A4" diff --git a/kgc/main/main.go b/kgc/main/main.go new file mode 100644 index 00000000..c77a0da2 --- /dev/null +++ b/kgc/main/main.go @@ -0,0 +1,248 @@ +package main + +import ( + "fmt" + "os" + "regexp" + "strings" + "github.com/OpenNHP/opennhp/kgc" + "github.com/OpenNHP/opennhp/kgc/user" + "github.com/urfave/cli/v2" +) + +func main() { + + app := cli.NewApp() + app.Name = "kgc" + app.Usage = "kgc command-line tool for CL-PKC" + app.Version = "1.0.0" + + keygenKgcCmd := &cli.Command{ + Name: "keygenkgc", + Usage: "Generate partial private key and declared public key", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "userid", + Aliases: []string{"u"}, + Usage: "User ID for generating partial private key and declared public key", + Required: false, + }, + }, + Action: func(c *cli.Context) error { + userID := c.String("userid") // Get userid from command line argument + if userID == "" { // If not provided on the command line, calls getUserID() + var err error + userID, err = getUserID() + if err != nil { + return fmt.Errorf("failed to get user ID: %v", err) + } + } + return generateKgcKey(userID) + }, + } + + // Define keygen command + keygenCmd := &cli.Command{ + Name: "keygen", + Usage: "Generate complete key pair for a user using user ID", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "userid", + Aliases: []string{"u"}, + Usage: "User ID for key generation", + Required: false}, + }, + Action: func(c *cli.Context) error { + userID := c.String("userid") + if userID == "" { + var err error + userID, err = getUserID() + if err != nil { + return fmt.Errorf("failed to get user ID: %v", err) + } + } + return generateUserKey(userID) + }, + } + + // Define sign command + signCmd := &cli.Command{ + Name: "sign", + Usage: "Sign a plaintext message", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "message", + Aliases: []string{"m"}, + Usage: "Plaintext message to sign", + Required: false}, + }, + Action: func(c *cli.Context) error { + userID := c.String("userid") + if userID == "" { + var err error + userID, err = getUserID() + if err != nil { + return fmt.Errorf("failed to get user ID: %v", err) + } + } + return signMessage(userID) + }, + } + + // Define verifysign command + verifySignCmd := &cli.Command{ + Name: "verifysign", + Usage: "Verify a signed message", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "signature", + Aliases: []string{"s"}, + Usage: "Signed message to verify", + Required: false}, + }, + Action: func(c *cli.Context) error { + userID, err := getUserID() + if err != nil { + return fmt.Errorf("failed to get user ID: %v", err) + } + return verifySignature(userID) + }, + } + + // Register all commands + app.Commands = []*cli.Command{ + keygenKgcCmd, + keygenCmd, + signCmd, + verifySignCmd, + } + + // Run application + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + //Verify that the public key is valid + /* user.ComputePAPrime(user.DA) + fmt.Printf("PAX_': %X\n", user.PAx_) + fmt.Printf("PAY_': %X\n", user.PAy_) + if user.PAx.Cmp(user.PAx_) != 0 { + fmt.Printf("Verification of PAX failed\n") + } else { + fmt.Printf("Verify PAX success\n") + } + if user.PAy.Cmp(user.PAy_) != 0 { + fmt.Printf("Verification of PAY failed\n") + } else { + fmt.Printf("Verify PAY success\n") + } */ +} + +func getUserID() (string, error) { + var userID string + for { + fmt.Print("Please enter user ID (e.g., email address or phone number): ") + fmt.Scanln(&userID) + if strings.Contains(userID, "@") { + if !isValidEmail(userID) { + fmt.Println("Invalid email address. Try again.") + continue + } + } else { + if !isValidPhoneNumber(userID) { + fmt.Println("Invalid phone number. Try again.") + continue + } + } + fmt.Println("Valid user ID.") + break + } + return userID, nil +} + +// isValidEmail is used to verify whether the email address is valid +func isValidEmail(email string) bool { + regex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + re, err := regexp.Compile(regex) + if err != nil { + fmt.Println("Regular compilation error:", err) + return false + } + return re.MatchString(email) +} + +// isValidPhoneNumber validates if the input is a valid phone number using regex +func isValidPhoneNumber(phone string) bool { + // This regex matches phone numbers like: + // 1234567890 (10 digits) + // +1-234-567-8901 (+1 followed by a 10-digit number) + regex := `^(?:\+?\d{1,3})?[-.\s]?\(?\d{1,4}?\)?[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,4}$` + re, err := regexp.Compile(regex) + if err != nil { + fmt.Println("Regex compilation error:", err) + return false + } + return re.MatchString(phone) +} + +// Command implementation logic +func generateKgcKey(userID string) error { + fmt.Println("Generating partial private key and declared public key...") + user.ProcessUserEmail(userID) + user.GenerateUserKeyPairSM2() + kgc.GenerateMasterKeyPairSM2() + kgc.GenerateWA(user.UAX, user.UAY) + kgc.CalculateHA(kgc.EntlA,kgc.IdA,kgc.A,kgc.B,kgc.Gx,kgc.Gy,kgc.PpubX,kgc.PpubY) + kgc.ComputeL(kgc.WAx,kgc.WAy,kgc.HA,kgc.N) + kgc.ComputeTA(kgc.W,kgc.L,kgc.Ms,kgc.N) + fmt.Printf("User partial private key: %X\n", kgc.TA) + fmt.Printf("User’s declares public key x coordinate: %X\nUser’s declares public key y coordinate: %X\n",kgc.WAx,kgc.WAy ) + return nil +} + +func generateUserKey(userID string) error { + fmt.Printf("Generating complete key pair for user ID: %s...\n", userID) + user.ProcessUserEmail(userID) + user.GenerateUserKeyPairSM2() + kgc.GenerateMasterKeyPairSM2() + kgc.GenerateWA(user.UAX, user.UAY) + kgc.CalculateHA(kgc.EntlA,kgc.IdA,kgc.A,kgc.B,kgc.Gx,kgc.Gy,kgc.PpubX,kgc.PpubY) + kgc.ComputeL(kgc.WAx,kgc.WAy,kgc.HA,kgc.N) + kgc.ComputeTA(kgc.W,kgc.L,kgc.Ms,kgc.N) + user.ComputeDA(kgc.TA,user.DA_,kgc.N) + user.ComputePA(kgc.WAx,kgc.WAy,kgc.PpubX,kgc.PpubY,kgc.L) + fmt.Printf("User private key: %X\n", user.DA) + fmt.Printf("User’s actual public key x coordinate: %X\nUser’s actual public key y coordinate: %X\n", user.PAx, user.PAy) + return nil +} + +func signMessage(userID string) error { + user.ProcessUserEmail(userID) + user.GenerateUserKeyPairSM2() + kgc.GenerateMasterKeyPairSM2() + kgc.GenerateWA(user.UAX, user.UAY) + kgc.CalculateHA(kgc.EntlA,kgc.IdA,kgc.A,kgc.B,kgc.Gx,kgc.Gy,kgc.PpubX,kgc.PpubY) + kgc.ComputeL(kgc.WAx,kgc.WAy,kgc.HA,kgc.N) + kgc.ComputeTA(kgc.W,kgc.L,kgc.Ms,kgc.N) + user.ComputeDA(kgc.TA,user.DA_,kgc.N) + user.SignMessageAndEncrypt(user.DA) + fmt.Println("Message successfully signed!") + fmt.Printf("Signature:\n") + fmt.Printf("r: %X\ns: %X\n", user.R, user.S) + return nil +} + +func verifySignature(userID string) error { + user.ProcessUserEmail(userID) + user.GenerateUserKeyPairSM2() + kgc.GenerateMasterKeyPairSM2() + kgc.GenerateWA(user.UAX, user.UAY) + kgc.CalculateHA(kgc.EntlA,kgc.IdA,kgc.A,kgc.B,kgc.Gx,kgc.Gy,kgc.PpubX,kgc.PpubY) + kgc.ComputeL(kgc.WAx,kgc.WAy,kgc.HA,kgc.N) + kgc.ComputeTA(kgc.W,kgc.L,kgc.Ms,kgc.N) + user.ComputeDA(kgc.TA,user.DA_,kgc.N) + user.ComputePA(kgc.WAx,kgc.WAy,kgc.PpubX,kgc.PpubY,kgc.L) + user.SignMessageAndEncrypt(user.DA) + user.VerifySignature(user.R,user.S,user.PAx,user.PAy,[32]byte(user.MessageHash)) + return nil +} \ No newline at end of file diff --git a/kgc/user/user.go b/kgc/user/user.go new file mode 100644 index 00000000..c1e6edc0 --- /dev/null +++ b/kgc/user/user.go @@ -0,0 +1,198 @@ +package user + +import ( + "crypto/rand" + "crypto/sha256" + "fmt" + "math/big" + "github.com/emmansun/gmsm/sm2" +) + +var ( + ID string //User ID + DA_ *big.Int //User secret value + UAX *big.Int // User public value + UAY *big.Int // User public value + DA *big.Int // User private key + PAx, PAy *big.Int + PAx_, PAy_ *big.Int + K *big.Int + R, S *big.Int + MessageHash []byte +) + +//receives the email and processes it +func ProcessUserEmail(id string) { + ID = id +} + +// GenerateUserKeyPairSM2,Generate user private key dA_ and public key UA based on SM2 +func GenerateUserKeyPairSM2() ( error) { + // Using the SM2 Curve + curve := sm2.P256() + // Randomly generate user private key dA_, the range is [1, n-1] + dA_, err := rand.Int(rand.Reader, curve.Params().N) + if err != nil { + return fmt.Errorf("failed to generate private key: %v", err) + } + if dA_.Cmp(big.NewInt(0)) == 0 { + dA_, err = rand.Int(rand.Reader, curve.Params().N) + if err != nil { + return fmt.Errorf("failed to regenerate private key: %v", err) + } + } + UAx, UAy := curve.ScalarBaseMult(dA_.Bytes()) + DA_ = dA_ + UAX = UAx + UAY = UAy + return nil +} + +// Calculate dA +func ComputeDA(tA, dA_ *big.Int, n *big.Int) *big.Int { + dA := new(big.Int).Set(tA) + dA.Add(dA, dA_) + dA.Mod(dA, n) + DA=dA + return DA +} + +// Calculate PA = WA + [l]Ppub +func ComputePA(WAx, WAy, PpubX, PpubY *big.Int, lInteger *big.Int) (*big.Int, *big.Int) { + curve := sm2.P256() + PpubXl, PpubYl := curve.ScalarMult(PpubX, PpubY, lInteger.Bytes()) + PAX, PAY := curve.Add(WAx, WAy, PpubXl, PpubYl) + PAx = PAX + PAy = PAY + return PAx, PAy +} + +// Calculate P'A = [dA]G +func ComputePAPrime(dA *big.Int) (*big.Int, *big.Int) { + curve := sm2.P256() + PAX_, PAY_ := curve.ScalarBaseMult(dA.Bytes()) + PAx_ = PAX_ + PAy_ = PAY_ + return PAx_, PAy_ +} + +//sign +func SignMessageAndEncrypt(dA *big.Int) error { + var message string + fmt.Print("Enter the plaintext message to sign: ") + fmt.Scanln(&message) + messageHash := sha256.Sum256([]byte(message)) + r, s, err := SignWithPrivateKey(dA, messageHash[:]) + if err != nil { + return fmt.Errorf("failed to sign message: %v", err) + } + R=r + S=s + MessageHash = messageHash[:] + //fmt.Printf("messageHash: %X\n",messageHash) + return nil +} + +// Signature logic: generate signature based on private key and message hash +func SignWithPrivateKey(dA *big.Int, messageHash []byte) (*big.Int, *big.Int, error) { + curve := sm2.P256() + k, err := GenerateRandomScalar() + if err != nil { + return nil, nil, fmt.Errorf("failed to generate random scalar: %v", err) + } + + // r = (k * G).x mod n + rX, _ := curve.ScalarBaseMult(k.Bytes()) + r := new(big.Int).Mod(rX, curve.Params().N) + if r.Sign() == 0 { + return nil, nil, fmt.Errorf("invalid r value, retrying") + } + + // s = (k^-1 * (hash + r * dA)) mod n + n := curve.Params().N + kInv := new(big.Int).ModInverse(k, n) + hash := new(big.Int).SetBytes(messageHash) + rdA := new(big.Int).Mul(r, dA) + s := new(big.Int).Mul(kInv, new(big.Int).Add(hash, rdA)) + s.Mod(s, n) + if s.Sign() == 0 { + return nil, nil, fmt.Errorf("invalid s value, retrying") + } + R=r + S=s + return R, S, nil +} + +// Generate a random scalar k for signature calculation +func GenerateRandomScalar() (*big.Int, error) { + curve := sm2.P256() + n := curve.Params().N + k, err := rand.Int(rand.Reader, n) + if err != nil { + return nil, fmt.Errorf("failed to generate random scalar: %v", err) + } + if k.Sign() == 0 { + k, err = rand.Int(rand.Reader, n) + if err != nil { + return nil, fmt.Errorf("failed to regenerate random scalar: %v", err) + } + } + K=k + return K, nil +} + +//Verify signature +func VerifySignature(R, S, PAx, PAy *big.Int, messageHash [32]byte) bool { + curve := sm2.P256() + + // Check R and S validity + n := curve.Params().N + if R.Sign() <= 0 || R.Cmp(n) >= 0 { + fmt.Println("Invalid R: out of range.") + return false + } + if S.Sign() <= 0 || S.Cmp(n) >= 0 { + fmt.Println("Invalid S: out of range.") + return false + } + + // Verify the public key is on the curve + if !curve.IsOnCurve(PAx, PAy) { + fmt.Println("Invalid public key: not on curve.") + return false + } + + // Compute w = S^-1 mod n + w := new(big.Int).ModInverse(S, n) + if w == nil { + fmt.Println("Failed to compute modular inverse of S.") + return false + } + + // Compute u1 = (messageHash * w) mod n and u2 = (R * w) mod n + u1 := new(big.Int).Mul(new(big.Int).SetBytes(messageHash[:]), w) + u1.Mod(u1, n) + u2 := new(big.Int).Mul(R, w) + u2.Mod(u2, n) + + // Compute (x1, y1) = u1*G + u2*(PAx, PAy) + x1, y1 := curve.ScalarBaseMult(u1.Bytes()) // u1*G + x2, y2 := curve.ScalarMult(PAx, PAy, u2.Bytes()) // u2*PA + x, y := curve.Add(x1, y1, x2, y2) // u1*G + u2*PA + + // Verify if R == x mod n + if x == nil || y == nil { + fmt.Println("Invalid point during verification.") + return false + } + v := new(big.Int).Mod(x, n) + if v.Cmp(R) == 0 { + fmt.Println("Signature verification succeeded!") + return true + } else { + fmt.Println("Signature verification failed!") + return false + } +} + +