-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathidentity.go
More file actions
129 lines (111 loc) · 3.55 KB
/
identity.go
File metadata and controls
129 lines (111 loc) · 3.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// identity.go — ECDSA P-256 identity for constellation nodes.
//
// Adapted from apps/cogos/bep_tls.go. Simplified to just key operations:
// generate, load, sign, verify, and NodeID derivation (SHA-256 of pubkey DER).
package constellation
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"os"
"path/filepath"
)
// NodeIdentity holds the ECDSA keypair and derived node ID.
type NodeIdentity struct {
PrivateKey *ecdsa.PrivateKey
PublicKey *ecdsa.PublicKey
NodeID string // hex-encoded SHA-256 of DER-encoded public key
}
// GenerateIdentity creates a new ECDSA P-256 keypair and derives the NodeID.
func GenerateIdentity() (*NodeIdentity, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, fmt.Errorf("generate key: %w", err)
}
return identityFromKey(key)
}
// SaveIdentity writes the private key to disk as PEM.
func SaveIdentity(id *NodeIdentity, dir string) error {
if err := os.MkdirAll(dir, 0700); err != nil {
return fmt.Errorf("create identity dir: %w", err)
}
keyDER, err := x509.MarshalECPrivateKey(id.PrivateKey)
if err != nil {
return fmt.Errorf("marshal key: %w", err)
}
keyPath := filepath.Join(dir, "node-key.pem")
f, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("create key file: %w", err)
}
defer func() { _ = f.Close() }()
return pem.Encode(f, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
}
// LoadIdentity reads an ECDSA private key from disk.
func LoadIdentity(dir string) (*NodeIdentity, error) {
keyPath := filepath.Join(dir, "node-key.pem")
data, err := os.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("read key: %w", err)
}
block, _ := pem.Decode(data)
if block == nil || block.Type != "EC PRIVATE KEY" {
return nil, fmt.Errorf("invalid PEM block type")
}
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parse key: %w", err)
}
return identityFromKey(key)
}
// identityFromKey derives NodeID from a private key.
func identityFromKey(key *ecdsa.PrivateKey) (*NodeIdentity, error) {
pubDER, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
return nil, fmt.Errorf("marshal public key: %w", err)
}
hash := sha256.Sum256(pubDER)
return &NodeIdentity{
PrivateKey: key,
PublicKey: &key.PublicKey,
NodeID: hex.EncodeToString(hash[:]),
}, nil
}
// Sign signs arbitrary data with the node's private key.
func (id *NodeIdentity) Sign(data []byte) ([]byte, error) {
hash := sha256.Sum256(data)
return ecdsa.SignASN1(rand.Reader, id.PrivateKey, hash[:])
}
// Verify checks a signature against a public key.
func Verify(pubKey *ecdsa.PublicKey, data, signature []byte) bool {
hash := sha256.Sum256(data)
return ecdsa.VerifyASN1(pubKey, hash[:], signature)
}
// FormatNodeID returns a short form of the node ID (first 12 hex chars).
func FormatNodeID(nodeID string) string {
if len(nodeID) > 12 {
return nodeID[:12]
}
return nodeID
}
// PublicKeyFromDER parses an ECDSA public key from DER bytes.
func PublicKeyFromDER(der []byte) (*ecdsa.PublicKey, error) {
pub, err := x509.ParsePKIXPublicKey(der)
if err != nil {
return nil, fmt.Errorf("parse public key: %w", err)
}
ecPub, ok := pub.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("not an ECDSA public key")
}
return ecPub, nil
}
// MarshalPublicKey returns the DER-encoded public key.
func (id *NodeIdentity) MarshalPublicKey() ([]byte, error) {
return x509.MarshalPKIXPublicKey(id.PublicKey)
}