71 lines
2.1 KiB
Go
71 lines
2.1 KiB
Go
package customer
|
|
|
|
import (
|
|
"crypto/elliptic"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"neuralhunt/internal/auth"
|
|
)
|
|
|
|
var identityRawURL = base64.RawURLEncoding
|
|
|
|
type rawPrivateJWK struct {
|
|
Kty string `json:"kty"`
|
|
Crv string `json:"crv"`
|
|
X string `json:"x"`
|
|
Y string `json:"y"`
|
|
D string `json:"d"`
|
|
}
|
|
|
|
type rawIdentityFile struct {
|
|
Version int `json:"version"`
|
|
PublicJWK auth.PublicJWK `json:"publicJwk"`
|
|
PrivateJWK rawPrivateJWK `json:"privateJwk"`
|
|
}
|
|
|
|
func padP256(b []byte) []byte {
|
|
out := make([]byte, 32)
|
|
if len(b) > 32 {
|
|
b = b[len(b)-32:]
|
|
}
|
|
copy(out[32-len(b):], b)
|
|
return out
|
|
}
|
|
|
|
// ValidateRawIdentity validates the portable raw CLI identity before it is
|
|
// written into a managed worker's private Docker volume. It intentionally does
|
|
// not accept the encrypted browser-export envelope because the service never
|
|
// needs or asks for the customer's export passphrase.
|
|
func ValidateRawIdentity(b []byte) error {
|
|
var id rawIdentityFile
|
|
if err := json.Unmarshal(b, &id); err != nil {
|
|
return fmt.Errorf("invalid identity JSON: %w", err)
|
|
}
|
|
if id.Version != 1 || id.PublicJWK.Kty != "EC" || id.PublicJWK.Crv != "P-256" || id.PrivateJWK.Kty != "EC" || id.PrivateJWK.Crv != "P-256" || id.PrivateJWK.D == "" {
|
|
return errors.New("unsupported identity; expected Neural Hunt version 1 P-256 raw identity")
|
|
}
|
|
db, err := identityRawURL.DecodeString(id.PrivateJWK.D)
|
|
if err != nil {
|
|
return errors.New("invalid private JWK encoding")
|
|
}
|
|
d := new(big.Int).SetBytes(db)
|
|
curve := elliptic.P256()
|
|
if d.Sign() <= 0 || d.Cmp(curve.Params().N) >= 0 {
|
|
return errors.New("invalid P-256 private scalar")
|
|
}
|
|
x, y := curve.ScalarBaseMult(padP256(db))
|
|
xs := identityRawURL.EncodeToString(padP256(x.Bytes()))
|
|
ys := identityRawURL.EncodeToString(padP256(y.Bytes()))
|
|
if xs != id.PublicJWK.X || ys != id.PublicJWK.Y || xs != id.PrivateJWK.X || ys != id.PrivateJWK.Y {
|
|
return errors.New("identity public/private key mismatch")
|
|
}
|
|
if _, err := auth.ClientID(id.PublicJWK); err != nil {
|
|
return fmt.Errorf("invalid public identity: %w", err)
|
|
}
|
|
return nil
|
|
}
|