+46
-3
@@ -41,8 +41,15 @@ type identityFile struct {
|
||||
PrivateJWK privateJWK `json:"privateJwk"`
|
||||
}
|
||||
|
||||
const identityKDFIterations = 250000
|
||||
|
||||
type encryptedIdentity struct {
|
||||
Version int `json:"version"`
|
||||
Format string `json:"format,omitempty"`
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
KDF string `json:"kdf,omitempty"`
|
||||
Iterations int `json:"iterations,omitempty"`
|
||||
Cipher string `json:"cipher,omitempty"`
|
||||
Salt string `json:"salt"`
|
||||
IV string `json:"iv"`
|
||||
Ciphertext string `json:"ciphertext"`
|
||||
@@ -161,6 +168,15 @@ func readIdentityImport(path, passphrase string) (identityFile, error) {
|
||||
if err := json.Unmarshal(b, &enc); err != nil {
|
||||
return identityFile{}, err
|
||||
}
|
||||
if enc.Format != "" && enc.Format != "neuralhunt-identity-export" {
|
||||
return identityFile{}, errors.New("unsupported identity export format")
|
||||
}
|
||||
if enc.KDF != "" && enc.KDF != "PBKDF2-HMAC-SHA256" {
|
||||
return identityFile{}, errors.New("unsupported identity KDF")
|
||||
}
|
||||
if enc.Cipher != "" && enc.Cipher != "AES-256-GCM" {
|
||||
return identityFile{}, errors.New("unsupported identity cipher")
|
||||
}
|
||||
salt, err := rawURL.DecodeString(enc.Salt)
|
||||
if err != nil {
|
||||
return identityFile{}, err
|
||||
@@ -173,7 +189,14 @@ func readIdentityImport(path, passphrase string) (identityFile, error) {
|
||||
if err != nil {
|
||||
return identityFile{}, err
|
||||
}
|
||||
key := pbkdf2SHA256([]byte(passphrase), salt, 250000, 32)
|
||||
iterations := enc.Iterations
|
||||
if iterations == 0 {
|
||||
iterations = identityKDFIterations // compatibility with v1 exports
|
||||
}
|
||||
if iterations < 100000 || iterations > 2000000 {
|
||||
return identityFile{}, errors.New("unsupported identity KDF iteration count")
|
||||
}
|
||||
key := pbkdf2SHA256([]byte(passphrase), salt, iterations, 32)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return identityFile{}, err
|
||||
@@ -193,6 +216,15 @@ func readIdentityImport(path, passphrase string) (identityFile, error) {
|
||||
if _, err := privateKeyFromIdentity(id); err != nil {
|
||||
return identityFile{}, err
|
||||
}
|
||||
if enc.ClientID != "" {
|
||||
cid, err := auth.ClientID(id.PublicJWK)
|
||||
if err != nil {
|
||||
return identityFile{}, err
|
||||
}
|
||||
if cid != enc.ClientID {
|
||||
return identityFile{}, errors.New("identity export client ID mismatch")
|
||||
}
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
@@ -200,6 +232,9 @@ func exportBrowserIdentity(path, passphrase string, id identityFile) error {
|
||||
if passphrase == "" {
|
||||
return errors.New("export requires --passphrase or NEURALHUNT_IDENTITY_PASSPHRASE")
|
||||
}
|
||||
if len(passphrase) < 12 {
|
||||
return errors.New("identity export passphrase must be at least 12 characters")
|
||||
}
|
||||
plain, err := json.Marshal(id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -212,7 +247,7 @@ func exportBrowserIdentity(path, passphrase string, id identityFile) error {
|
||||
if _, err := rand.Read(iv); err != nil {
|
||||
return err
|
||||
}
|
||||
key := pbkdf2SHA256([]byte(passphrase), salt, 250000, 32)
|
||||
key := pbkdf2SHA256([]byte(passphrase), salt, identityKDFIterations, 32)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -222,7 +257,15 @@ func exportBrowserIdentity(path, passphrase string, id identityFile) error {
|
||||
return err
|
||||
}
|
||||
ct := gcm.Seal(nil, iv, plain, nil)
|
||||
enc := encryptedIdentity{Version: 1, Salt: rawURL.EncodeToString(salt), IV: rawURL.EncodeToString(iv), Ciphertext: rawURL.EncodeToString(ct)}
|
||||
cid, err := auth.ClientID(id.PublicJWK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := encryptedIdentity{
|
||||
Version: 1, Format: "neuralhunt-identity-export", ClientID: cid,
|
||||
KDF: "PBKDF2-HMAC-SHA256", Iterations: identityKDFIterations, Cipher: "AES-256-GCM",
|
||||
Salt: rawURL.EncodeToString(salt), IV: rawURL.EncodeToString(iv), Ciphertext: rawURL.EncodeToString(ct),
|
||||
}
|
||||
b, err := json.MarshalIndent(enc, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user