Clean up.
[crypto_lab2.git] / labo2-fsharp / CryptoFile / API.fs
1 namespace CryptoFile
2
3 open System
4 open System.IO
5
6 // To read and write metadata.
7 type internal Metadata (d: (string * string) list) =
8 // Read metadata from a stream.
9 new (stream : Stream) =
10 let reader = new BinaryReader (stream)
11 let length = reader.ReadByte () |> int
12 Metadata ([for i in 1..length -> reader.ReadString (), reader.ReadString ()])
13
14 // Write metadata to a stream.
15 member this.WriteTo (stream : Stream) =
16 let writer = new BinaryWriter (stream)
17 writer.Write (byte d.Length)
18 List.iter (fun (key : string, value : string) -> writer.Write key; writer.Write value) d
19
20 // May raise 'KeyNotFoundException'.
21 member this.get (key: string) : string =
22 List.pick (function
23 | (k, v) when k = key -> Some (v)
24 | _ -> None) d
25
26 module API =
27 module internal MetadataKeys =
28 let filename = "filename"
29 let modificationTime = "file-modification-time"
30
31 let internal (@@) a1 a2 = Array.append a1 a2
32
33 let generatKeysPair : Key * Key = Crypto.generateRSAKeysPair
34
35 let encryptFile (inputFilePath : string) (outputFilePath : string) (signaturePrivKey: Key) (cryptPubKey : Key) =
36 let keyAES, keyMAC, iv = Crypto.rand 16, Crypto.rand 32, Crypto.rand 16
37 let fileInfo = FileInfo (inputFilePath)
38 use inputStream = fileInfo.OpenRead ()
39 use outputStream = new FileStream (outputFilePath, FileMode.Create, FileAccess.Write)
40 use writer = new BinaryWriter (outputStream)
41
42 outputStream.Position <- 32L + 256L // Skips mac and signature. They will be written later.
43
44 Crypto.encryptRSA cryptPubKey (keyAES @@ keyMAC @@ iv) |> writer.Write
45
46 // Plaintext -> cryptoStream -> hmacStream -> cyphertext.
47 let hmacStream, hmac = Crypto.HMACStream keyMAC outputStream
48 use cryptoStream = Crypto.encryptAES keyAES iv hmacStream
49 use cryptoWriter = new BinaryWriter (cryptoStream)
50
51 // Write the file metadata.
52 let metaData = Metadata ([MetadataKeys.filename, fileInfo.Name
53 MetadataKeys.modificationTime, fileInfo.LastWriteTimeUtc.Ticks.ToString ()])
54 metaData.WriteTo cryptoStream
55
56 // Write the content of the file.
57 inputStream.CopyTo cryptoStream
58 cryptoStream.FlushFinalBlock ()
59
60 // Write the HMAC at the begining of the file.
61 outputStream.Position <- 0L
62 writer.Write hmac.Hash
63
64 // Write the signature.
65 Crypto.signRSA signaturePrivKey hmac.Hash |> writer.Write
66
67 // May raise one of the following error:
68 // * IntegrityError
69 // * SignatureMismatch
70 // * UnableToDecryptAESKeys
71 let decryptFile (sourceFilePath : string) (targetDirPath : string) (signaturePubKey: Key) (decryptPrivKey : Key) =
72 use inputStream = new FileStream (sourceFilePath, FileMode.Open, FileAccess.Read)
73 use reader = new BinaryReader (inputStream)
74 let mac = reader.ReadBytes 32
75 let signature = reader.ReadBytes 256
76 let keys =
77 try reader.ReadBytes 256 |> Crypto.decryptRSA decryptPrivKey
78 with
79 | :? Security.Cryptography.CryptographicException -> raise UnableToDecryptAESKeys
80 let keyAES = keys.[0..15]
81 let keyMAC = keys.[16..47]
82 let iv = keys.[48..63]
83
84 // Integrity validation.
85 let mac' = Crypto.ComputeHMAC keyMAC inputStream
86 if mac' <> mac then
87 raise IntegrityError
88
89 // Authentication validation.
90 if not <| Crypto.verifySignRSA signaturePubKey mac' signature then
91 raise SignatureMismatch
92
93 // Decrypt metadata.
94 inputStream.Position <- 32L + 256L + 256L
95 use cryptoStream = Crypto.decryptAES keyAES iv inputStream
96 let metadata = Metadata cryptoStream
97
98 // Create the file and write its content and metadata.
99 let filePath = Path.Combine (targetDirPath, metadata.get MetadataKeys.filename)
100 let modificationTime = DateTime (metadata.get MetadataKeys.modificationTime |> int64)
101 let fileInfo = FileInfo filePath
102 using (fileInfo.Create ()) <| fun outputStream -> cryptoStream.CopyTo outputStream
103 fileInfo.LastWriteTimeUtc <- modificationTime
104