Skip to content
Snippets Groups Projects
Commit 0d9346db authored by netrino's avatar netrino
Browse files

Initial vanitiy generator implementation

parent eec4b550
No related branches found
No related tags found
No related merge requests found
......@@ -84,33 +84,12 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str
protoUser := createNewUser(rngStream, cmixGrp, e2eGrp)
// Get current client version
currentVersion, err := version.ParseVersion(SEMVER)
if err != nil {
return errors.WithMessage(err, "Could not parse version string.")
}
// Create Storage
passwordStr := string(password)
storageSess, err := storage.New(storageDir, passwordStr, protoUser,
currentVersion, cmixGrp, e2eGrp, rngStreamGen)
err = checkVersionAndSetupStorage(def, storageDir, password, protoUser,
cmixGrp, e2eGrp, rngStreamGen, false, registrationCode)
if err != nil {
return err
}
// Save NDF to be used in the future
storageSess.SetBaseNDF(def)
//store the registration code for later use
storageSess.SetRegCode(registrationCode)
//move the registration state to keys generated
err = storageSess.ForwardRegistrationStatus(storage.KeyGenComplete)
if err != nil {
return errors.WithMessage(err, "Failed to denote state "+
"change in session")
}
//TODO: close the session
return nil
}
......@@ -135,29 +114,39 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [
protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp)
// Get current client version
currentVersion, err := version.ParseVersion(SEMVER)
err = checkVersionAndSetupStorage(def, storageDir, password, protoUser,
cmixGrp, e2eGrp, rngStreamGen, true, "")
if err != nil {
return errors.WithMessage(err, "Could not parse version string.")
return err
}
//TODO: close the session
return nil
}
// Create Storage
passwordStr := string(password)
storageSess, err := storage.New(storageDir, passwordStr, protoUser,
currentVersion, cmixGrp, e2eGrp, rngStreamGen)
// NewVanityClient creates a user with a receptionID that starts with the supplied prefix
// It creates client storage, generates keys, connects, and registers
// with the network. Note that this does not register a username/identity, but
// merely creates a new cryptographic identity for adding such information
// at a later date.
func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCode string, userIdPrefix string) error {
jww.INFO.Printf("NewVanityClient()")
// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
rngStream := rngStreamGen.GetStream()
// Parse the NDF
def, err := parseNDF(ndfJSON)
if err != nil {
return err
}
cmixGrp, e2eGrp := decodeGroups(def)
// Save NDF to be used in the future
storageSess.SetBaseNDF(def)
protoUser := createNewVanityUser(rngStream, cmixGrp, e2eGrp, userIdPrefix)
//move the registration state to indicate registered with permissioning
err = storageSess.ForwardRegistrationStatus(
storage.PermissioningComplete)
err = checkVersionAndSetupStorage(def, storageDir, password, protoUser,
cmixGrp, e2eGrp, rngStreamGen, false, registrationCode)
if err != nil {
return errors.WithMessage(err, "Failed to denote state "+
"change in session")
return err
}
//TODO: close the session
......@@ -583,3 +572,43 @@ func decodeGroups(ndf *ndf.NetworkDefinition) (cmixGrp, e2eGrp *cyclic.Group) {
return cmixGrp, e2eGrp
}
// checkVersionAndSetupStorage is common code shared by NewClient, NewPrecannedClient and NewVanityClient
// it checks client version and creates a new storage for user data
func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, storageDir string, password []byte,
protoUser user.User, cmixGrp, e2eGrp *cyclic.Group, rngStreamGen *fastRNG.StreamGenerator,
isPrecanned bool, registrationCode string) error {
// Get current client version
currentVersion, err := version.ParseVersion(SEMVER)
if err != nil {
return errors.WithMessage(err, "Could not parse version string.")
}
// Create Storage
passwordStr := string(password)
storageSess, err := storage.New(storageDir, passwordStr, protoUser,
currentVersion, cmixGrp, e2eGrp, rngStreamGen)
if err != nil {
return err
}
// Save NDF to be used in the future
storageSess.SetBaseNDF(def)
if !isPrecanned {
//store the registration code for later use
storageSess.SetRegCode(registrationCode)
//move the registration state to keys generated
err = storageSess.ForwardRegistrationStatus(storage.KeyGenComplete)
} else {
//move the registration state to indicate registered with permissioning
err = storageSess.ForwardRegistrationStatus(storage.PermissioningComplete)
}
if err != nil {
return errors.WithMessage(err, "Failed to denote state "+
"change in session")
}
return nil
}
......@@ -17,6 +17,10 @@ import (
"gitlab.com/xx_network/crypto/xx"
"gitlab.com/xx_network/primitives/id"
"math/rand"
"regexp"
"runtime"
"strings"
"sync"
)
const (
......@@ -134,3 +138,126 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic.
ReceptionRSA: rsaKey,
}
}
// createNewVanityUser generates an identity for cMix
// The identity's ReceptionID is not random but starts with the supplied prefix
func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix string) user.User {
// CMIX Keygen
// FIXME: Why 256 bits? -- this is spec but not explained, it has
// to do with optimizing operations on one side and still preserves
// decent security -- cite this.
cMixKeyBytes, err := csprng.GenerateInGroup(cmix.GetPBytes(), 256, rng)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
// DH Keygen
// FIXME: Why 256 bits? -- this is spec but not explained, it has
// to do with optimizing operations on one side and still preserves
// decent security -- cite this. Why valid for BOTH e2e and cmix?
e2eKeyBytes, err := csprng.GenerateInGroup(e2e.GetPBytes(), 256, rng)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
// RSA Keygen (4096 bit defaults)
transmissionRsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
// Salt, UID, etc gen
transmissionSalt := make([]byte, SaltSize)
n, err := csprng.NewSystemRNG().Read(transmissionSalt)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
if n != SaltSize {
jww.FATAL.Panicf("transmissionSalt size too small: %d", n)
}
transmissionID, err := xx.NewID(transmissionRsaKey.GetPublic(), transmissionSalt, id.User)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
receptionRsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
var mu sync.Mutex // just in case more than one go routine tries to access receptionSalt and receptionID
done := make(chan struct{})
found:= make(chan bool)
wg:= &sync.WaitGroup{}
cores := runtime.NumCPU()
var receptionSalt []byte
var receptionID *id.ID
pref := prefix
ignoreCase := false
// check if case-insensitivity is enabled
if strings.HasPrefix(prefix, "(?i)") {
pref = strings.ToLower(pref[4:])
ignoreCase = true
}
// Check if prefix contains valid Base64 characters
match, _ := regexp.MatchString("^[A-Za-z0-9+/]+$", pref)
if match == false {
jww.FATAL.Panicf("Prefix contains non-Base64 characters")
}
jww.INFO.Printf("Vanity userID generation started. Prefix: %s Ignore-Case: %v NumCPU: %d", pref, ignoreCase, cores)
for w := 0; w < cores; w++{
wg.Add(1)
go func() {
rSalt := make([]byte, SaltSize)
for {
select {
case <- done:
defer wg.Done()
return
default:
n, err = csprng.NewSystemRNG().Read(rSalt)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
if n != SaltSize {
jww.FATAL.Panicf("receptionSalt size too small: %d", n)
}
rID, err := xx.NewID(receptionRsaKey.GetPublic(), rSalt, id.User)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
id := rID.String()
if ignoreCase {
id = strings.ToLower(id)
}
if strings.HasPrefix(id, pref) {
mu.Lock()
receptionID = rID
receptionSalt = rSalt
mu.Unlock()
found <- true
defer wg.Done()
return
}
}
}
}()
}
// wait for a solution then close the done channel to signal the workers to exit
<- found
close(done)
wg.Wait()
return user.User{
TransmissionID: transmissionID.DeepCopy(),
TransmissionSalt: transmissionSalt,
TransmissionRSA: transmissionRsaKey,
ReceptionID: receptionID.DeepCopy(),
ReceptionSalt: receptionSalt,
ReceptionRSA: receptionRsaKey,
Precanned: false,
CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes),
E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes),
}
}
......@@ -11,6 +11,7 @@ package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
jww "github.com/spf13/jwalterweatherman"
)
......@@ -29,5 +30,9 @@ var initCmd = &cobra.Command{
}
func init() {
initCmd.Flags().StringP("userid-prefix", "", "",
"Desired prefix of userID to brute force when running init command. Prepend (?i) for case-insensitive. Only Base64 characters are valid.")
_ = viper.BindPFlag("userid-prefix", initCmd.Flags().Lookup("userid-prefix"))
rootCmd.AddCommand(initCmd)
}
......@@ -306,7 +306,7 @@ func createClient() *api.Client {
storeDir := viper.GetString("session")
regCode := viper.GetString("regcode")
precannedID := viper.GetUint("sendid")
userIDprefix := viper.GetString("userid-prefix")
//create a new client if none exist
if _, err := os.Stat(storeDir); os.IsNotExist(err) {
// Load NDF
......@@ -319,11 +319,17 @@ func createClient() *api.Client {
if precannedID != 0 {
err = api.NewPrecannedClient(precannedID,
string(ndfJSON), storeDir, []byte(pass))
} else {
if userIDprefix != "" {
err = api.NewVanityClient(string(ndfJSON), storeDir,
[]byte(pass), regCode, userIDprefix)
} else {
err = api.NewClient(string(ndfJSON), storeDir,
[]byte(pass), regCode)
}
}
if err != nil {
jww.FATAL.Panicf("%+v", err)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment