forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsecretbuffer.jl
197 lines (169 loc) · 5.67 KB
/
secretbuffer.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
Base.SecretBuffer()
An [`IOBuffer`](@ref)-like object where the contents will be securely wiped when garbage collected.
It is considered best practice to wipe the buffer using `Base.shred!(::SecretBuffer)` as
soon as the secure data are no longer required. When initializing with existing data, the
`SecretBuffer!` method is highly recommended to securely zero the passed argument. Avoid
initializing with and converting to `String`s as they are unable to be securely zeroed.
# Examples
```jldoctest
julia> s = Base.SecretBuffer()
SecretBuffer("*******")
julia> write(s, 's', 'e', 'c', 'r', 'e', 't')
6
julia> seek(s, 0); Char(read(s, UInt8))
's': ASCII/Unicode U+0073 (category Ll: Letter, lowercase)
julia> Base.shred!(s)
SecretBuffer("*******")
julia> eof(s)
true
```
"""
mutable struct SecretBuffer <: IO
data::Vector{UInt8}
size::Int
ptr::Int
function SecretBuffer(; sizehint=128)
s = new(Vector{UInt8}(undef, sizehint), 0, 1)
finalizer(final_shred!, s)
return s
end
end
"""
SecretBuffer(str::AbstractString)
A convenience constructor to initialize a `SecretBuffer` from a non-secret string.
Strings are bad at keeping secrets because they are unable to be securely
zeroed or destroyed. Therefore, avoid using this constructor with secret data.
Instead of starting with a string, either construct the `SecretBuffer`
incrementally with `SecretBuffer()` and [`write`](@ref), or use a `Vector{UInt8}` with
the `Base.SecretBuffer!(::Vector{UInt8})` constructor.
"""
SecretBuffer(str::AbstractString) = SecretBuffer(String(str))
function SecretBuffer(str::String)
buf = codeunits(str)
s = SecretBuffer(sizehint=length(buf))
for c in buf
write(s, c)
end
seek(s, 0)
s
end
convert(::Type{SecretBuffer}, s::AbstractString) = SecretBuffer(String(s))
"""
SecretBuffer!(data::Vector{UInt8})
Initialize a new `SecretBuffer` from `data`, securely zeroing `data` afterwards.
"""
function SecretBuffer!(d::Vector{UInt8})
len = length(d)
s = SecretBuffer(sizehint=len)
for i in 1:len
write(s, d[i])
end
seek(s, 0)
securezero!(d)
s
end
function unsafe_SecretBuffer!(s::Cstring)
if s == C_NULL
throw(ArgumentError("cannot convert NULL to SecretBuffer"))
end
len = Int(ccall(:strlen, Csize_t, (Cstring,), s))
unsafe_SecretBuffer!(convert(Ptr{UInt8}, s), len)
end
function unsafe_SecretBuffer!(p::Ptr{UInt8}, len=1)
if p == C_NULL
throw(ArgumentError("cannot convert NULL to SecretBuffer"))
end
s = SecretBuffer(sizehint=len)
for i in 1:len
write(s, unsafe_load(p, i))
end
seek(s, 0)
unsafe_securezero!(p, len)
s
end
show(io::IO, s::SecretBuffer) = print(io, "SecretBuffer(\"*******\")")
# Unlike other IO objects, equality is computed by value for convenience
==(s1::SecretBuffer, s2::SecretBuffer) = (s1.ptr == s2.ptr) && (s1.size == s2.size) && (UInt8(0) == _bufcmp(s1.data, s2.data, min(s1.size, s2.size)))
# Also attempt a constant time buffer comparison algorithm — the length of the secret might be
# inferred by a timing attack, but not its values.
@noinline function _bufcmp(data1::Vector{UInt8}, data2::Vector{UInt8}, sz::Int)
res = UInt8(0)
for i = 1:sz
res |= xor(data1[i], data2[i])
end
return res
end
# All SecretBuffers hash the same to avoid leaking information or breaking consistency with ==
const _sb_hash = UInt === UInt32 ? 0x111c0925 : 0xb06061e370557428
hash(s::SecretBuffer, h::UInt) = hash(_sb_hash, h)
function write(io::SecretBuffer, b::UInt8)
if io.ptr > length(io.data)
# We need to resize! the array: do this manually to ensure no copies are left behind
newdata = Vector{UInt8}(undef, (io.size+16)*2)
copyto!(newdata, io.data)
securezero!(io.data)
io.data = newdata
end
io.size == io.ptr-1 && (io.size += 1)
io.data[io.ptr] = b
io.ptr += 1
return 1
end
function write(io::IO, s::SecretBuffer)
nb = 0
for i in 1:s.size
nb += write(io, s.data[i])
end
return nb
end
cconvert(::Type{Cstring}, s::SecretBuffer) = unsafe_convert(Cstring, s)
function unsafe_convert(::Type{Cstring}, s::SecretBuffer)
# Ensure that no nuls appear in the valid region
if any(==(0x00), s.data[i] for i in 1:s.size)
throw(ArgumentError("`SecretBuffers` containing nul bytes cannot be converted to C strings"))
end
# Add a hidden nul byte just past the end of the valid region
p = s.ptr
s.ptr = s.size + 1
write(s, '\0')
s.ptr = p
s.size -= 1
return Cstring(unsafe_convert(Ptr{Cchar}, s.data))
end
seek(io::SecretBuffer, n::Integer) = (io.ptr = max(min(n+1, io.size+1), 1); io)
seekend(io::SecretBuffer) = seek(io, io.size+1)
skip(io::SecretBuffer, n::Integer) = seek(io, position(io) + n)
bytesavailable(io::SecretBuffer) = io.size - io.ptr + 1
position(io::SecretBuffer) = io.ptr-1
eof(io::SecretBuffer) = io.ptr > io.size
isempty(io::SecretBuffer) = io.size == 0
function peek(io::SecretBuffer, ::Type{UInt8})
eof(io) && throw(EOFError())
return io.data[io.ptr]
end
function read(io::SecretBuffer, ::Type{UInt8})
eof(io) && throw(EOFError())
byte = io.data[io.ptr]
io.ptr += 1
return byte
end
function final_shred!(s::SecretBuffer)
!isshredded(s) && @async @warn("a SecretBuffer was `shred!`ed by the GC; use `shred!` manually after use to minimize exposure.")
shred!(s)
end
function shred!(s::SecretBuffer)
securezero!(s.data)
s.ptr = 1
s.size = 0
return s
end
isshredded(s::SecretBuffer) = all(iszero, s.data)
function shred!(f::Function, x)
try
f(x)
finally
shred!(x)
end
end