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 }