github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/service/windows/securestring/securestring.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Copyright 2015 Cloudbase Solutions
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  //
     5  // +build windows
     6  
     7  package securestring
     8  
     9  import (
    10  	"bytes"
    11  	"encoding/binary"
    12  	"encoding/hex"
    13  	"fmt"
    14  	"syscall"
    15  	"unsafe"
    16  
    17  	"github.com/juju/errors"
    18  )
    19  
    20  //sys protectData(input uintptr, szDataDescr uint32, entropy uintptr, reserved uint32, prompt uint32, flags uint, output uintptr) (err error) [failretval==0] = crypt32.CryptProtectData
    21  //sys unprotectData(input uintptr, szDataDescr uint32, entropy uintptr, reserved uint32, prompt uint32, flags uint, output uintptr) (err error) [failretval==0] = crypt32.CryptUnprotectData
    22  
    23  // blob is the struct type we shall be making the syscalls on, it contains a
    24  // pointer to the start of the actual data and its respective length in bytes
    25  type blob struct {
    26  	length uint32
    27  	data   *byte
    28  }
    29  
    30  // getData returns an uint16 array that contains the data pointed to by blob.data
    31  // The return value of this function will be passed to syscall.UTF16ToString
    32  // to return the plain text result
    33  func (b *blob) getData() []uint16 {
    34  	data := (*[1 << 16]uint16)(unsafe.Pointer(b.data))[:b.length/2]
    35  	return data
    36  }
    37  
    38  // getDataAsBytes returns a byte array with the data pointed to by blob.data
    39  func (b *blob) getDataAsBytes() []byte {
    40  	data := (*[1 << 30]byte)(unsafe.Pointer(b.data))[:b.length]
    41  	return data
    42  }
    43  
    44  // convertToUTF16 converts the utf8 string to utf16
    45  func convertToUTF16(a string) ([]byte, error) {
    46  	u16, err := syscall.UTF16FromString(a)
    47  	if err != nil {
    48  		return nil, errors.Annotate(err, "Failed to convert string to UTF16")
    49  	}
    50  	buf := &bytes.Buffer{}
    51  	if err := binary.Write(buf, binary.LittleEndian, u16); err != nil {
    52  		return nil, errors.Annotate(err, "Failed to convert UTF16 to bytes")
    53  	}
    54  	return buf.Bytes(), nil
    55  }
    56  
    57  // Encrypt encrypts a string provided as input into a hexadecimal string
    58  // the output corresponds to the output of ConvertFrom-SecureString:
    59  func Encrypt(input string) (string, error) {
    60  	// we need to convert UTF8 to UTF16 before sending it into CryptProtectData
    61  	// to be compatible with the way powershell does it
    62  	data, err := convertToUTF16(input)
    63  	if err != nil {
    64  		return "", err
    65  	}
    66  	inputBlob := blob{uint32(len(data)), &data[0]}
    67  	outputBlob := blob{}
    68  
    69  	err = protectData(uintptr(unsafe.Pointer(&inputBlob)), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outputBlob)))
    70  	if err != nil {
    71  		return "", fmt.Errorf("Failed to encrypt %s, error: %s", input, err)
    72  	}
    73  	defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(outputBlob.data)))
    74  	output := outputBlob.getDataAsBytes()
    75  	// the result is a slice of bytes, which we must encode into hexa
    76  	// to match ConvertFrom-SecureString's output before returning it
    77  	h := hex.EncodeToString([]byte(output))
    78  	return h, nil
    79  }
    80  
    81  // Decrypt converts the output from a call to ConvertFrom-SecureString
    82  // back to the original input string and returns it
    83  func Decrypt(input string) (string, error) {
    84  	// first we decode the hexadecimal string into a raw slice of bytes
    85  	data, err := hex.DecodeString(input)
    86  	if err != nil {
    87  		return "", err
    88  	}
    89  	inputBlob := blob{uint32(len(data)), &data[0]}
    90  	outputBlob := blob{}
    91  
    92  	err = unprotectData(uintptr(unsafe.Pointer(&inputBlob)), 0, 0, 0, 0, 0,
    93  		uintptr(unsafe.Pointer(&outputBlob)))
    94  	if err != nil {
    95  		return "", fmt.Errorf("Failed to decrypt %s, error: &s", input, err)
    96  	}
    97  	defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(outputBlob.data)))
    98  
    99  	a := outputBlob.getData()
   100  	return syscall.UTF16ToString(a), nil
   101  }