Skip to content
Cannot retrieve contributors at this time
130 lines (114 sloc) 4.78 KB
Copyright 2016, Cossack Labs Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
See the License for the specific language governing permissions and
limitations under the License.
package base
import (
// GetDataLengthFromAcraStruct unpack data length value from AcraStruct
func GetDataLengthFromAcraStruct(data []byte) int {
dataLengthBlock := data[GetMinAcraStructLength()-DataLengthSize : GetMinAcraStructLength()]
return int(binary.LittleEndian.Uint64(dataLengthBlock))
// GetMinAcraStructLength returns minimal length of AcraStruct
// because in golang we can't declare byte array as constant we need to calculate length of TagBegin in runtime
// or hardcode as constant and maintain len(TagBegin) == CONST_VALUE
func GetMinAcraStructLength() int {
return len(TagBegin) + KeyBlockLength + DataLengthSize
// Errors show incorrect AcraStruct length
var (
ErrIncorrectAcraStructTagBegin = errors.New("AcraStruct has incorrect TagBegin")
ErrIncorrectAcraStructLength = errors.New("AcraStruct has incorrect length")
ErrIncorrectAcraStructDataLength = errors.New("AcraStruct has incorrect data length value")
// ErrNoPrivateKeys is returned when DecryptRotatedAcrastruct is given an empty key list
var ErrNoPrivateKeys = errors.New("cannot decrypt AcraStruct with empty key list")
// ValidateAcraStructLength check that data has minimal length for AcraStruct and data block equal to data length in AcraStruct
func ValidateAcraStructLength(data []byte) error {
baseLength := GetMinAcraStructLength()
if len(data) < baseLength {
return ErrIncorrectAcraStructLength
if !bytes.Equal(data[:len(TagBegin)], TagBegin) {
return ErrIncorrectAcraStructTagBegin
dataLength := GetDataLengthFromAcraStruct(data)
if dataLength != len(data[GetMinAcraStructLength():]) {
return ErrIncorrectAcraStructDataLength
return nil
// DecryptAcrastruct returns plaintext data from AcraStruct, decrypting it using Themis SecureCell in Seal mode,
// using zone as context and privateKey as decryption key.
// Returns error if decryption failed.
func DecryptAcrastruct(data []byte, privateKey *keys.PrivateKey, zone []byte) ([]byte, error) {
if err := ValidateAcraStructLength(data); err != nil {
return nil, err
innerData := data[len(TagBegin):]
pubkey := &keys.PublicKey{Value: innerData[:PublicKeyLength]}
smessage := message.New(privateKey, pubkey)
symmetricKey, err := smessage.Unwrap(innerData[PublicKeyLength:KeyBlockLength])
if err != nil {
return []byte{}, err
var length uint64
// convert from little endian
err = binary.Read(bytes.NewReader(innerData[KeyBlockLength:KeyBlockLength+DataLengthSize]), binary.LittleEndian, &length)
if err != nil {
return []byte{}, err
scell := cell.New(symmetricKey, cell.ModeSeal)
decrypted, err := scell.Unprotect(innerData[KeyBlockLength+DataLengthSize:], nil, zone)
// fill zero symmetric_key
if err != nil {
return []byte{}, err
return decrypted, nil
// DecryptRotatedAcrastruct tries decrypting an AcraStruct with a set of rotated keys.
// It either returns decrypted data if one of the keys succeeds, or an error if none is good.
func DecryptRotatedAcrastruct(data []byte, privateKeys []*keys.PrivateKey, zone []byte) ([]byte, error) {
var err error = ErrNoPrivateKeys
var decryptedData []byte
for _, privateKey := range privateKeys {
decryptedData, err = DecryptAcrastruct(data, privateKey, zone)
if err == nil {
return decryptedData, nil
return nil, err
// CheckPoisonRecord checks if AcraStruct could be decrypted using Poison Record private key.
// Returns true if AcraStruct is poison record, returns false otherwise.
// Returns error if Poison record key is not found.
func CheckPoisonRecord(data []byte, keystorage keystore.PoisonKeyStore) (bool, error) {
// If we fail to get poison record keys, propagate the error assuming it is a poison record.
poisonKeys, err := keystorage.GetPoisonPrivateKeys()
if err != nil {
return true, err
defer utils.ZeroizePrivateKeys(poisonKeys)
// Try decrypting the data. It is a poison record if it can be decrypted without an error.
_, err = DecryptRotatedAcrastruct(data, poisonKeys, nil)
return err == nil, nil