-
Notifications
You must be signed in to change notification settings - Fork 3
/
header.ts
145 lines (121 loc) · 3.2 KB
/
header.ts
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
import {
bytes,
decodeBase64,
decodeBase64Url,
encodeBase64,
encodeBase64Url,
} from "./deps.ts";
import {
DEFAULT_RECORD_SIZE,
HEADER_LENGTH_MIN,
IDLEN_LENGTH,
RECORD_SIZE_MAX,
RECORD_SIZE_MIN,
RS_LENGTH,
SALT_LENGTH,
} from "./const.ts";
import { Salt } from "./salt.ts";
/**
* Options for Header class.
*/
export interface HeaderOptions {
salt?: Salt | Uint8Array | ArrayBuffer;
rs?: number;
keyid?: Uint8Array;
}
/**
* Header define Encryption Content-Coding Header (https://www.rfc-editor.org/rfc/rfc8188#section-2.1)
*/
export class Header {
public readonly salt: Salt;
public readonly rs: number;
public readonly keyid: Uint8Array;
public get idlen(): number {
return this.keyid.byteLength;
}
public get byteLength(): number {
return HEADER_LENGTH_MIN + this.idlen;
}
constructor(
{
salt = new Salt(),
rs = DEFAULT_RECORD_SIZE,
keyid = new Uint8Array(),
}: HeaderOptions,
) {
if (rs < RECORD_SIZE_MIN || rs > RECORD_SIZE_MAX) {
throw new RecordSizeError(rs);
}
this.salt = salt instanceof Salt ? salt : new Salt(salt);
this.rs = rs;
this.keyid = keyid;
}
public static fromBytes(buf: ArrayBuffer): Header {
if (buf.byteLength < HEADER_LENGTH_MIN) {
throw new HeaderSizeError(buf);
}
const dv = new DataView(buf);
let cur = 0;
const salt = dv.buffer.slice(0, SALT_LENGTH);
cur += SALT_LENGTH;
const rs = dv.getUint32(cur);
cur += RS_LENGTH;
const idlen = dv.getUint8(cur);
cur += IDLEN_LENGTH;
const keyid = dv.buffer.slice(cur, cur + idlen);
cur += idlen;
return new Header({
salt: new Salt(salt),
rs,
keyid: new Uint8Array(keyid),
});
}
public static fromBase64(b: string): Header {
return Header.fromBytes(decodeBase64(b).buffer);
}
public static fromBase64Url(b: string): Header {
return Header.fromBytes(decodeBase64Url(b).buffer);
}
public toBytes(): ArrayBuffer {
const bytes = new Uint8Array(this.byteLength);
const dv = new DataView(bytes.buffer);
bytes.set(this.salt);
dv.setUint32(SALT_LENGTH, this.rs);
dv.setUint8(SALT_LENGTH + RS_LENGTH, this.idlen);
bytes.set(this.keyid, SALT_LENGTH + RS_LENGTH + IDLEN_LENGTH);
return bytes.buffer;
}
public toBase64(): string {
return encodeBase64(this.toBytes());
}
public toBase64Url(): string {
return encodeBase64Url(this.toBytes());
}
public equals(other: Header): boolean {
return bytes.equals(this.salt, other.salt) &&
this.rs === other.rs &&
bytes.equals(this.keyid, other.keyid);
}
}
/**
* RecordSizeError is thrown when record size exceed maximum size of is less than
* minimum record size.
*/
export class RecordSizeError extends Error {
constructor(rs: number) {
super(
`record size must be comprised between ${RECORD_SIZE_MIN} and ${RECORD_SIZE_MAX}: got ${rs}`,
);
}
}
/**
* HeaderSizeError is thrown when encryption content-coding header is less than
* minimum header size.
*/
export class HeaderSizeError extends Error {
constructor(bytes: ArrayBuffer) {
super(
`header block must be at least ${HEADER_LENGTH_MIN} byte long: got ${bytes.byteLength}`,
);
}
}