Ejemplo n.º 1
0
// TOTPFromBytes converts a byte array to a totp object
// it stores the state of the TOTP object, like the key, the current counter, the client offset,
// the total amount of verification failures and the last time a verification happened
func TOTPFromBytes(encryptedMessage []byte, issuer string) (*Totp, error) {

	// init the cryptoengine
	engine, err := cryptoengine.InitCryptoEngine(issuer)
	if err != nil {
		return nil, err
	}

	// decrypt the message
	data, err := engine.Decrypt(encryptedMessage)
	if err != nil {
		return nil, err
	}

	// new reader
	reader := bytes.NewReader([]byte(data.Text))

	// otp object
	otp := new(Totp)

	// get the lenght
	lenght := make([]byte, 4)
	_, err = reader.Read(lenght) // read the 4 bytes for the total lenght
	if err != nil && err != io.EOF {
		return otp, err
	}

	totalSize := bigendian.FromInt([4]byte{lenght[0], lenght[1], lenght[2], lenght[3]})
	buffer := make([]byte, totalSize-4)
	_, err = reader.Read(buffer)
	if err != nil && err != io.EOF {
		return otp, err
	}

	// skip the total bytes size
	startOffset := 0
	// read key size
	endOffset := startOffset + 4
	keyBytes := buffer[startOffset:endOffset]
	keySize := bigendian.FromInt([4]byte{keyBytes[0], keyBytes[1], keyBytes[2], keyBytes[3]})

	// read the key
	startOffset = endOffset
	endOffset = startOffset + keySize
	otp.key = buffer[startOffset:endOffset]

	// read the counter
	startOffset = endOffset
	endOffset = startOffset + 8
	b := buffer[startOffset:endOffset]
	otp.counter = [8]byte{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]}

	// read the digits
	startOffset = endOffset
	endOffset = startOffset + 4
	b = buffer[startOffset:endOffset]
	otp.digits = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) //

	// read the issuer size
	startOffset = endOffset
	endOffset = startOffset + 4
	b = buffer[startOffset:endOffset]
	issuerSize := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]})

	// read the issuer string
	startOffset = endOffset
	endOffset = startOffset + issuerSize
	otp.issuer = string(buffer[startOffset:endOffset])

	// read the account size
	startOffset = endOffset
	endOffset = startOffset + 4
	b = buffer[startOffset:endOffset]
	accountSize := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]})

	// read the account string
	startOffset = endOffset
	endOffset = startOffset + accountSize
	otp.account = string(buffer[startOffset:endOffset])

	// read the steps
	startOffset = endOffset
	endOffset = startOffset + 4
	b = buffer[startOffset:endOffset]
	otp.stepSize = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]})

	// read the offset
	startOffset = endOffset
	endOffset = startOffset + 4
	b = buffer[startOffset:endOffset]
	otp.clientOffset = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]})

	// read the total failuers
	startOffset = endOffset
	endOffset = startOffset + 4
	b = buffer[startOffset:endOffset]
	otp.totalVerificationFailures = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]})

	// read the offset
	startOffset = endOffset
	endOffset = startOffset + 8
	b = buffer[startOffset:endOffset]
	ts := bigendian.FromUint64([8]byte{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]})
	otp.lastVerificationTime = time.Unix(int64(ts), 0)

	// read the hash type
	startOffset = endOffset
	endOffset = startOffset + 4
	b = buffer[startOffset:endOffset]
	hashType := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]})

	switch hashType {
	case 1:
		otp.hashFunction = crypto.SHA256
		break
	case 2:
		otp.hashFunction = crypto.SHA512
		break
	default:
		otp.hashFunction = crypto.SHA1
	}

	return otp, err
}
Ejemplo n.º 2
0
// ToBytes serialises a TOTP object in a byte array
// Sizes:         4        4      N     8       4        4        N         4          N      4     4          4               8                 4
// Format: |total_bytes|key_size|key|counter|digits|issuer_size|issuer|account_size|account|steps|offset|total_failures|verification_time|hashFunction_type|
// hashFunction_type: 0 = SHA1; 1 = SHA256; 2 = SHA512
// The data is encrypted using the cryptoengine library (which is a wrapper around the golang NaCl library)
// TODO:
// 1- improve sizes. For instance the hashFunction_type could be a short.
func (otp *Totp) ToBytes() ([]byte, error) {

	// check Totp initialization
	if err := totpHasBeenInitialized(otp); err != nil {
		return nil, err
	}

	var buffer bytes.Buffer

	// caluclate the length of the key and create its byte representation
	keySize := len(otp.key)
	keySizeBytes := bigendian.ToInt(keySize) //bigEndianInt(keySize)

	// caluclate the length of the issuer and create its byte representation
	issuerSize := len(otp.issuer)
	issuerSizeBytes := bigendian.ToInt(issuerSize)

	// caluclate the length of the account and create its byte representation
	accountSize := len(otp.account)
	accountSizeBytes := bigendian.ToInt(accountSize)

	totalSize := 4 + 4 + keySize + 8 + 4 + 4 + issuerSize + 4 + accountSize + 4 + 4 + 4 + 8 + 4
	totalSizeBytes := bigendian.ToInt(totalSize)

	// at this point we are ready to write the data to the byte buffer
	// total size
	if _, err := buffer.Write(totalSizeBytes[:]); err != nil {
		return nil, err
	}

	// key
	if _, err := buffer.Write(keySizeBytes[:]); err != nil {
		return nil, err
	}
	if _, err := buffer.Write(otp.key); err != nil {
		return nil, err
	}

	// counter
	counterBytes := bigendian.ToUint64(otp.getIntCounter())
	if _, err := buffer.Write(counterBytes[:]); err != nil {
		return nil, err
	}

	// digits
	digitBytes := bigendian.ToInt(otp.digits)
	if _, err := buffer.Write(digitBytes[:]); err != nil {
		return nil, err
	}

	// issuer
	if _, err := buffer.Write(issuerSizeBytes[:]); err != nil {
		return nil, err
	}
	if _, err := buffer.WriteString(otp.issuer); err != nil {
		return nil, err
	}

	// account
	if _, err := buffer.Write(accountSizeBytes[:]); err != nil {
		return nil, err
	}
	if _, err := buffer.WriteString(otp.account); err != nil {
		return nil, err
	}

	// steps
	stepsBytes := bigendian.ToInt(otp.stepSize)
	if _, err := buffer.Write(stepsBytes[:]); err != nil {
		return nil, err
	}

	// offset
	offsetBytes := bigendian.ToInt(otp.clientOffset)
	if _, err := buffer.Write(offsetBytes[:]); err != nil {
		return nil, err
	}

	// total_failures
	totalFailuresBytes := bigendian.ToInt(otp.totalVerificationFailures)
	if _, err := buffer.Write(totalFailuresBytes[:]); err != nil {
		return nil, err
	}

	// last verification time
	verificationTimeBytes := bigendian.ToUint64(uint64(otp.lastVerificationTime.Unix()))
	if _, err := buffer.Write(verificationTimeBytes[:]); err != nil {
		return nil, err
	}

	// has_function_type
	switch otp.hashFunction {
	case crypto.SHA256:
		sha256Bytes := bigendian.ToInt(1)
		if _, err := buffer.Write(sha256Bytes[:]); err != nil {
			return nil, err
		}
		break
	case crypto.SHA512:
		sha512Bytes := bigendian.ToInt(2)
		if _, err := buffer.Write(sha512Bytes[:]); err != nil {
			return nil, err
		}
		break
	default:
		sha1Bytes := bigendian.ToInt(0)
		if _, err := buffer.Write(sha1Bytes[:]); err != nil {
			return nil, err
		}
	}

	// encrypt the TOTP bytes
	engine, err := cryptoengine.InitCryptoEngine(otp.issuer)
	if err != nil {
		return nil, err
	}

	// init the message to be encrypted
	message, err := cryptoengine.NewMessage(buffer.String(), message_type)
	if err != nil {
		return nil, err
	}

	// encrypt it
	encryptedMessage, err := engine.NewEncryptedMessage(message)
	if err != nil {
		return nil, err
	}

	return encryptedMessage.ToBytes()

}