1+ namespace Paket.Core.Common
2+
3+ open System
4+ open System.IO
5+ open System.Net .Http
6+ open System.Security .Cryptography
7+ open System.Text
8+ open Paket
9+ open Paket.Logging
10+
11+ type private AesKey < 'T > = private AesKey of 'T
12+ type private AesIV < 'T > = private AesIV of 'T
13+
14+ type PlainTextPassword = PlainTextPassword of string
15+ type AesEncryptedPassword = private AesEncryptedPassword of string
16+ type DPApiEncryptedPassword = private DPApiEncryptedPassword of string
17+
18+ [<RequireQualifiedAccess>]
19+ type EncryptedPassword =
20+ | Aes of AesEncryptedPassword
21+ | DPApi of DPApiEncryptedPassword
22+ with
23+ override x.ToString () =
24+ match x with
25+ | Aes ( AesEncryptedPassword password) | DPApi ( DPApiEncryptedPassword password) -> password
26+
27+ type AesSalt = private AesSalt of string
28+ type DPApiSalt = private DPApiSalt of string
29+
30+ [<RequireQualifiedAccess>]
31+ type Salt =
32+ | Aes of AesSalt
33+ | DPApi of DPApiSalt
34+ with
35+ override x.ToString () =
36+ match x with
37+ | Aes ( AesSalt salt) | DPApi ( DPApiSalt salt) -> salt
38+
39+ [<RequireQualifiedAccess>]
40+ module private AesSalt =
41+ let [<Literal>] saltSeparator = " SALT_SEPARATOR"
42+
43+ let private toBytes ( AesKey keyString , AesIV ivString ) =
44+ ( AesKey << Convert.FromBase64String) keyString,
45+ ( AesIV << Convert.FromBase64String) ivString
46+
47+ let encode (( AesKey key ): AesKey < byte []>,( AesIV iv ): AesIV < byte []>) =
48+ seq { key; iv}
49+ |> Seq.map Convert.ToBase64String
50+ |> String.concat saltSeparator
51+ |> AesSalt
52+
53+ let decode ( AesSalt saltString ) =
54+ match saltString.Split([| saltSeparator|], StringSplitOptions.None) with
55+ | [| keyString; ivString|] -> ( AesKey keyString, AesIV ivString)
56+ | _ -> failwith " Should never happen"
57+
58+ let (| IsAesSalt | _ |) ( str : string ) =
59+ if str.Contains saltSeparator then
60+ Some <| AesSalt str
61+ else
62+ None
63+
64+ [<RequireQualifiedAccess>]
65+ module Aes =
66+ let private encryptString ( password : string ) =
67+ let writePassword ( cryptoStream : CryptoStream ) =
68+ use streamWriter = new StreamWriter( cryptoStream)
69+ streamWriter.Write( password)
70+
71+ use aes = Aes.Create()
72+ let encryptor = aes.CreateEncryptor()
73+ use memoryStream = new MemoryStream()
74+ use cryptoStream = new CryptoStream( memoryStream, encryptor, CryptoStreamMode.Write)
75+ writePassword cryptoStream
76+ let encryptedPassword = memoryStream.ToArray()
77+ encryptedPassword, aes.Key, aes.IV
78+
79+ let private decryptBytes ( encryptedBytes : byte []) key iv =
80+ use aes = Aes.Create()
81+ aes.Key <- key
82+ aes.IV <- iv
83+ let decryptor = aes.CreateDecryptor()
84+ use memoryStream = new MemoryStream( encryptedBytes)
85+ use cryptoStream = new CryptoStream( memoryStream, decryptor, CryptoStreamMode.Read)
86+ use streamReader = new StreamReader( cryptoStream)
87+ let password = streamReader.ReadToEnd()
88+ password
89+
90+ let private serialize password ( key : byte []) ( iv : byte []) =
91+ ( AesEncryptedPassword << Convert.ToBase64String) password,
92+ AesSalt.encode ( AesKey key, AesIV iv)
93+
94+ let private deserialize ( AesEncryptedPassword password ) aesSalt =
95+ let ( AesKey key , AesIV iv ) = AesSalt.decode aesSalt
96+
97+ Convert.FromBase64String password,
98+ Convert.FromBase64String key,
99+ Convert.FromBase64String iv
100+
101+ let encrypt ( PlainTextPassword password ) =
102+ encryptString password
103+ |||> serialize
104+
105+ let decrypt encryptedPassword aesSalt =
106+ deserialize encryptedPassword aesSalt
107+ |||> decryptBytes
108+ |> PlainTextPassword
109+
110+ [<RequireQualifiedAccess>]
111+ module private DPApiSalt =
112+ let private fillRandomBytes =
113+ let provider = RandomNumberGenerator.Create()
114+ ( fun ( b : byte []) -> provider.GetBytes( b))
115+
116+ let encode bytes =
117+ bytes
118+ |> Convert.ToBase64String
119+ |> DPApiSalt
120+
121+ let getRandomSalt () =
122+ let saltSize = 8
123+ let saltBytes = Array.create saltSize ( new Byte() )
124+ fillRandomBytes( saltBytes)
125+ saltBytes
126+
127+ [<RequireQualifiedAccess>]
128+ module DPApi =
129+ let encrypt ( PlainTextPassword password ) =
130+ let salt = DPApiSalt.getRandomSalt()
131+ let encryptedPassword =
132+ try
133+ ProtectedData.Protect( Encoding.UTF8.GetBytes password, salt, DataProtectionScope.CurrentUser)
134+ with | :? CryptographicException as e ->
135+ if verbose then
136+ verbosefn " could not protect password: %s \n for current user" e.Message
137+ ProtectedData.Protect( Encoding.UTF8.GetBytes password, salt, DataProtectionScope.LocalMachine)
138+ encryptedPassword |> Convert.ToBase64String |> DPApiEncryptedPassword,
139+ salt |> DPApiSalt.encode
140+
141+ let decrypt ( encryptedPassword : string ) ( salt : string ) =
142+ ProtectedData.Unprotect( Convert.FromBase64String encryptedPassword, Convert.FromBase64String salt, DataProtectionScope.CurrentUser)
143+ |> Encoding.UTF8.GetString
144+ |> PlainTextPassword
145+
146+ [<RequireQualifiedAccess>]
147+ module Crypto =
148+ let encrypt plainTextPassword =
149+ if Utils.isWindows then
150+ let ( dpApiPassword , dpApiSalt ) = DPApi.encrypt plainTextPassword
151+ ( EncryptedPassword.DPApi dpApiPassword, Salt.DPApi dpApiSalt)
152+ else
153+ let ( aesPassword , aesSalt ) = Aes.encrypt plainTextPassword
154+ ( EncryptedPassword.Aes aesPassword, Salt.Aes aesSalt)
155+
156+ let decrypt password salt =
157+ match salt with
158+ | AesSalt.IsAesSalt salt ->
159+ Aes.decrypt ( AesEncryptedPassword password) salt
160+ | _ ->
161+ DPApi.decrypt password salt
0 commit comments