@google/gemini-cli-core 0.54.0-nightly.20260727.g3818efbbf → 0.54.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/core/geminiChat.js +8 -2
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +26 -0
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/services/fileKeychain.js +22 -12
- package/dist/src/services/fileKeychain.js.map +1 -1
- package/dist/src/services/gcmTruncationReproduction.test.d.ts +6 -0
- package/dist/src/services/gcmTruncationReproduction.test.js +135 -0
- package/dist/src/services/gcmTruncationReproduction.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/google-gemini-cli-core-0.54.0-nightly.20260722.gf743ab579.tgz +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
import { promises as fs } from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import * as os from 'node:os';
|
|
10
|
+
import * as crypto from 'node:crypto';
|
|
11
|
+
import { FileKeychain } from './fileKeychain.js';
|
|
12
|
+
describe('AES-GCM Tag Length Verification', () => {
|
|
13
|
+
let tempDir;
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
// Create a unique temporary directory for test isolation
|
|
16
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gemini-test-keychain-'));
|
|
17
|
+
vi.stubEnv('GEMINI_CLI_HOME', tempDir);
|
|
18
|
+
});
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
vi.unstubAllEnvs();
|
|
21
|
+
// Clean up the temporary directory
|
|
22
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
it('should use a secure 128-bit (16-byte) AES-GCM authentication tag and standard 12-byte IV', async () => {
|
|
25
|
+
const keychain = new FileKeychain();
|
|
26
|
+
const service = 'test-service';
|
|
27
|
+
const account = 'test-account';
|
|
28
|
+
const password = 'secure-password-123';
|
|
29
|
+
// 1. Save credentials to trigger encryption and file write
|
|
30
|
+
await keychain.setPassword(service, account, password);
|
|
31
|
+
// 2. Read the raw encrypted file from disk
|
|
32
|
+
const credentialsFilePath = path.join(tempDir, '.gemini', 'gemini-credentials.json');
|
|
33
|
+
const rawEncryptedData = await fs.readFile(credentialsFilePath, 'utf-8');
|
|
34
|
+
// 3. Parse the encrypted data format (iv:authTag:encrypted)
|
|
35
|
+
const parts = rawEncryptedData.split(':');
|
|
36
|
+
expect(parts).toHaveLength(3);
|
|
37
|
+
const ivHex = parts[0];
|
|
38
|
+
const authTagHex = parts[1];
|
|
39
|
+
// 4. Verify the lengths of the components
|
|
40
|
+
const ivBuffer = Buffer.from(ivHex, 'hex');
|
|
41
|
+
const authTagBuffer = Buffer.from(authTagHex, 'hex');
|
|
42
|
+
// IV should be exactly 12 bytes (96 bits) by default
|
|
43
|
+
expect(ivBuffer.length).toBe(12);
|
|
44
|
+
expect(ivHex.length).toBe(24);
|
|
45
|
+
// Authentication Tag should be exactly 16 bytes (128 bits)
|
|
46
|
+
expect(authTagBuffer.length).toBe(16);
|
|
47
|
+
expect(authTagHex.length).toBe(32); // 32 hex characters
|
|
48
|
+
// Assert that the tag is NOT truncated to 4 bytes (32 bits)
|
|
49
|
+
expect(authTagBuffer.length).not.toBe(4);
|
|
50
|
+
expect(authTagHex.length).not.toBe(8); // 8 hex characters
|
|
51
|
+
// 5. Verify that decryption works correctly with the 16-byte tag
|
|
52
|
+
const decryptedPassword = await keychain.getPassword(service, account);
|
|
53
|
+
expect(decryptedPassword).toBe(password);
|
|
54
|
+
});
|
|
55
|
+
it('should support both 12-byte and 16-byte IVs for backward compatibility', async () => {
|
|
56
|
+
const keychain = new FileKeychain();
|
|
57
|
+
const service = 'test-service';
|
|
58
|
+
const account = 'test-account';
|
|
59
|
+
const password = 'secure-password-123';
|
|
60
|
+
// 1. Save credentials to trigger encryption and file write (generates 12-byte IV)
|
|
61
|
+
await keychain.setPassword(service, account, password);
|
|
62
|
+
// 2. Verify 12-byte IV decryption works
|
|
63
|
+
let decryptedPassword = await keychain.getPassword(service, account);
|
|
64
|
+
expect(decryptedPassword).toBe(password);
|
|
65
|
+
// 3. Manually simulate a legacy 16-byte IV credentials file
|
|
66
|
+
const credentialsFilePath = path.join(tempDir, '.gemini', 'gemini-credentials.json');
|
|
67
|
+
const legacyIv = crypto.randomBytes(16);
|
|
68
|
+
const encryptionKey = keychain
|
|
69
|
+
.encryptionKey;
|
|
70
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey, legacyIv, {
|
|
71
|
+
authTagLength: 16,
|
|
72
|
+
});
|
|
73
|
+
let encrypted = cipher.update(JSON.stringify({ [service]: { [account]: password } }), 'utf8', 'hex');
|
|
74
|
+
encrypted += cipher.final('hex');
|
|
75
|
+
const authTag = cipher.getAuthTag();
|
|
76
|
+
const legacyPayload = legacyIv.toString('hex') +
|
|
77
|
+
':' +
|
|
78
|
+
authTag.toString('hex') +
|
|
79
|
+
':' +
|
|
80
|
+
encrypted;
|
|
81
|
+
await fs.writeFile(credentialsFilePath, legacyPayload, 'utf-8');
|
|
82
|
+
// 4. Verify 16-byte IV decryption works successfully (backward compatibility)
|
|
83
|
+
decryptedPassword = await keychain.getPassword(service, account);
|
|
84
|
+
expect(decryptedPassword).toBe(password);
|
|
85
|
+
});
|
|
86
|
+
it('should reject decryption of a credentials file with a truncated tag', async () => {
|
|
87
|
+
const keychain = new FileKeychain();
|
|
88
|
+
const service = 'test-service';
|
|
89
|
+
const account = 'test-account';
|
|
90
|
+
const password = 'secure-password-123';
|
|
91
|
+
// 1. Save credentials to trigger encryption and file write
|
|
92
|
+
await keychain.setPassword(service, account, password);
|
|
93
|
+
// 2. Read the raw encrypted file from disk
|
|
94
|
+
const credentialsFilePath = path.join(tempDir, '.gemini', 'gemini-credentials.json');
|
|
95
|
+
const rawEncryptedData = await fs.readFile(credentialsFilePath, 'utf-8');
|
|
96
|
+
// 3. Parse the encrypted data format (iv:authTag:encrypted)
|
|
97
|
+
const parts = rawEncryptedData.split(':');
|
|
98
|
+
expect(parts).toHaveLength(3);
|
|
99
|
+
const ivHex = parts[0];
|
|
100
|
+
const authTagHex = parts[1];
|
|
101
|
+
const encryptedHex = parts[2];
|
|
102
|
+
// 4. Create a truncated 4-byte tag (8 hex characters)
|
|
103
|
+
const truncatedTagHex = authTagHex.substring(0, 8);
|
|
104
|
+
const truncatedEncryptedData = `${ivHex}:${truncatedTagHex}:${encryptedHex}`;
|
|
105
|
+
// 5. Overwrite the credentials file with the truncated-tag payload
|
|
106
|
+
await fs.writeFile(credentialsFilePath, truncatedEncryptedData, 'utf-8');
|
|
107
|
+
// 6. Attempt to retrieve the password and verify it throws a clear, handled validation error
|
|
108
|
+
await expect(keychain.getPassword(service, account)).rejects.toThrow('Corrupted credentials file detected');
|
|
109
|
+
});
|
|
110
|
+
it('should reject decryption of a credentials file with a truncated IV', async () => {
|
|
111
|
+
const keychain = new FileKeychain();
|
|
112
|
+
const service = 'test-service';
|
|
113
|
+
const account = 'test-account';
|
|
114
|
+
const password = 'secure-password-123';
|
|
115
|
+
// 1. Save credentials to trigger encryption and file write
|
|
116
|
+
await keychain.setPassword(service, account, password);
|
|
117
|
+
// 2. Read the raw encrypted file from disk
|
|
118
|
+
const credentialsFilePath = path.join(tempDir, '.gemini', 'gemini-credentials.json');
|
|
119
|
+
const rawEncryptedData = await fs.readFile(credentialsFilePath, 'utf-8');
|
|
120
|
+
// 3. Parse the encrypted data format (iv:authTag:encrypted)
|
|
121
|
+
const parts = rawEncryptedData.split(':');
|
|
122
|
+
expect(parts).toHaveLength(3);
|
|
123
|
+
const ivHex = parts[0];
|
|
124
|
+
const authTagHex = parts[1];
|
|
125
|
+
const encryptedHex = parts[2];
|
|
126
|
+
// 4. Create a truncated 4-byte IV (8 hex characters)
|
|
127
|
+
const truncatedIvHex = ivHex.substring(0, 8);
|
|
128
|
+
const truncatedEncryptedData = `${truncatedIvHex}:${authTagHex}:${encryptedHex}`;
|
|
129
|
+
// 5. Overwrite the credentials file with the truncated-IV payload
|
|
130
|
+
await fs.writeFile(credentialsFilePath, truncatedEncryptedData, 'utf-8');
|
|
131
|
+
// 6. Attempt to retrieve the password and verify it throws a clear, handled validation error
|
|
132
|
+
await expect(keychain.getPassword(service, account)).rejects.toThrow('Corrupted credentials file detected');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=gcmTruncationReproduction.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcmTruncationReproduction.test.js","sourceRoot":"","sources":["../../../src/services/gcmTruncationReproduction.test.ts"],"names":[],"mappings":"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"}
|