Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version issue with age-encrypted sops files #17

Open
humphd opened this issue Mar 15, 2024 · 2 comments
Open

Version issue with age-encrypted sops files #17

humphd opened this issue Mar 15, 2024 · 2 comments

Comments

@humphd
Copy link

humphd commented Mar 15, 2024

Thank you for making this. I couldn't believe it when I went looking for a TS age implementation, and lo and behold, you had made an official one. Amazing!

My current use case is being able to decrypt pieces of an age-encrypted sops file in JS. We

Here's an example of the kind of thing I want to parse, where I need to decrypt the value key, and my AGE public key is listed as a recipient:

value: ENC[AES256_GCM,data:asgm,iv:535n5Dj8DJ+XY5KuAYK2nGPKpA2H5Er7eLNPChQxEWg=,tag:TEohl6v4sHcrQK8IAx8p8w==,type:str]
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age:
        - recipient: age19j4d6v9j7rx5fs629fu387qz4zmlpsqjexa4s08tkfrrmfdl5cwqjlaupd
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqTXJvbVVsSzVMM1RIcUpY
            RTBPZTdwN3RZUGJDV0p0eDFCaTJzZm1YU0RFCk1YbFg3djFBR3RjQmduaTBBUlFy
            WFR2S2JacC8xUnh4Y29GMk8wK3NGREUKLS0tIFFvRGlHNmt1RGtVVEZ3eUpWbk96
            a1lpeVFqVDlZaHRFV1c5V0pMbXI4RkkKrLaOy3LVv9Uap07S8xQi+CJr9i2tcbZR
            VAgOMocpDRQU6AsiU+suZQ0X+Zz9Obb1oRTez84FSBwoOojYBbjLxA==
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2024-03-15T13:31:49Z"
    mac: ENC[AES256_GCM,data:/qN53oo7iWrZRLm8OopnkK/4IpOYTb+wo2PA3EHQIzuWxnpigxdbnCA8bWdB4v79Xaeu8AJUZOa5kzWh8+we9mPI8M2zj6rwYnbuqrYnF/wzGmEW6PQylw2LN8WNKnlNRWf/a3XP1/v2LXyQCkdtmadejEgG3BjH5gmKLWskl2E=,iv:g+Ppyo9LxFgoCOZTCnesdwwE91d2j8isg91KfXPFntM=,tag:I4b0IyqjxbTJ6+yrCHvtjw==,type:str]
    pgp: []
    unencrypted_suffix: _unencrypted
    version: 3.8.1

Here's my first attempt to get that decryption key:

import age from "age-encryption";
import { readFile } from "node:fs/promises";
import yaml from "js-yaml";

async function getPublicAgeKey(privateAgeKey: string) {
    const { identityToRecipient } = await age();
    return identityToRecipient(privateAgeKey);
}

async function decryptSopsFile(sopsFile: string, privateAgeKey: string) {
    const { Decrypter } = await age();
    const doc: any = yaml.load(await readFile(sopsFile, "utf8"));
    const ageConfig = doc.sops.age;

    const pubKey = await getPublicAgeKey(privateAgeKey);
    const config = ageConfig.find((config: any) => config.recipient === pubKey);

    const decrypter = new Decrypter();
    decrypter.addIdentity(privateAgeKey);
    const decryptionKey = decrypter.decrypt(config.enc.trim(), "text");

    // TODO ...
}

async function main() {
    await decryptSopsFile(process.env.SOPS_FILE, process.env.AGE_KEY);
}

main();

When I run this, I get the following error:

/workspaces/DeepStructure/node_modules/.pnpm/age-encryption@0.1.5/node_modules/age-encryption/dist/format.js:91
        throw Error("invalid version " + versionLine);
              ^


Error: invalid version null
    at parseHeader (/workspaces/DeepStructure/node_modules/.pnpm/age-encryption@0.1.5/node_modules/age-encryption/dist/format.js:91:15)
    at Decrypter.decrypt (/workspaces/DeepStructure/node_modules/.pnpm/age-encryption@0.1.5/node_modules/age-encryption/dist/index.js:115:19)
    at decryptSopsFile (/workspaces/DeepStructure/misc/sops/sops-js/src/sops.ts:20:37)
    at main (/workspaces/DeepStructure/misc/sops/sops-js/src/index.ts:32:5)

Which seems to be

if (versionLine !== "age-encryption.org/v1") {

On my system I'm using:

$ age --version
v1.1.1

Do I need to pass more info in order to be able to do this? Use a different version somehow? Or maybe it's not possible?

Thanks for helping me understand what is and isn't possible.

@humphd
Copy link
Author

humphd commented Mar 17, 2024

I played with this some more and was able to get it. I needed to extract the base64 encoded key:

function getEncryptionKeyForRecipient(
    sopsFile: string,
    privateAgeKey: string
) {
    const { Decrypter } = await age();
    const doc = await loadSopsFile(sopsFile);
    if (!Array.isArray(doc?.sops?.age)) {
        throw new Error("missing sops age metadata");
    }

    const sopsAgeConfig = doc.sops.age;
    const pubKey = await getPublicAgeKey(privateAgeKey);
    const { enc } = sopsAgeConfig.find(
        (config: SopsAgeConfig) => config.recipient === pubKey
    );
    if (!enc) {
        throw new Error("no matching recipient found in age config");
    }

    const decrypter = new Decrypter();
    decrypter.addIdentity(privateAgeKey);

    const regex =
        /-----BEGIN AGE ENCRYPTED FILE-----\s*([\s\S]*?)\s*-----END AGE ENCRYPTED FILE-----/;
    const matches = enc.match(regex);

    if (!(matches && matches[1])) {
        throw new Error("unable to extract age encryption key");
    }

    const base64String = matches[1].trim();
    const encrypted = Buffer.from(base64String, "base64");
    const decryptionKey = decrypter.decrypt(encrypted, "uint8array");

    return decryptionKey;
}

I'm surprised that I couldn't use the whole -----BEGIN AGE ENCRYPTED FILE-----... block, but perhaps that's just my own ignorance showing.

@humphd
Copy link
Author

humphd commented Mar 27, 2024

I ended up making an npm package to work with sops and age in TS/JS: https://github.com/humphd/sops-age

Thanks for making this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant