Welcome to the Inedo Forums! Check out the Forums Guide for help getting started.
If you are experiencing any issues with the forum software, please visit the Contact Form on our website and let us know!
How to download private GPG key of an APT repository
-
I've got an APT repository on ProGet that uses a GPG key to sign the repository, but I'm needing to get the private key because I need to do some stuff with it locally.
Where can I find the GPG key at? Looking through ProGet's data directory (on a Docker setup) isn't showing any GPG files.
-
It didn't look like there was anything in the UI to get the key out of my instance, so I started looking around in the database to see if I could find anything:
I found the
Feeds
table which looked like it might contain the key in theSecretKeys
field, but GPG isn't recognizing that as valid key if I put it into a file.Is there something I'm missing? I'm more than glad to do whatever I need to from the database manually if this isn't something that could be added to the UI in the near future.
-
@hwittenborn there isn't a way to download the keys once created; it's probably not something we'll add to the UI in the near future since it doesn't seem trivial
The keys are indeed stored in the
SecretKeys
, but they're encrypted using theEncryptionKey
stored in your ProGet configuration file.You can try to decrypt them with this method (note: use EncryptionMode.Aes128)
/// <summary> /// Decrypts data using the specified encryption mode. /// </summary> /// <param name="data">The data to decrypt.</param> /// <param name="mode">Method used to decrypt the data.</param> /// <returns>Decrypted data.</returns> /// <exception cref="ArgumentNullException"><paramref name="data"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="mode"/> is invalid.</exception> public byte[] Decrypt(byte[] data, EncryptionMode mode) { if (data == null) throw new ArgumentNullException(nameof(data)); byte[]? key = mode switch { EncryptionMode.Aes128 => this.Aes128Key ?? throw new InvalidOperationException("Cannot decrypt value; there is no legacy encryption key defined."), EncryptionMode.Aes256 => this.Aes256Key ?? throw new InvalidOperationException("Cannot decrypt value; there is no AES256 encryption key defined."), EncryptionMode.None => null, _ => throw new ArgumentOutOfRangeException(nameof(mode)) }; if (key == null) return data; var iv = new byte[16]; Buffer.BlockCopy(data, 0, iv, 0, 8); Buffer.BlockCopy(data, data.Length - 8, iv, 8, 8); using var buffer = new MemoryStream(data.Length - 16); buffer.Write(data, 8, data.Length - 16); buffer.Position = 0; using var aes = Aes.Create(); aes.Key = key; aes.IV = iv; aes.Padding = PaddingMode.PKCS7; using var cryptoStream = new CryptoStream(buffer, aes.CreateDecryptor(), CryptoStreamMode.Read); var output = new byte[SlimBinaryFormatter.ReadLength(cryptoStream)]; int bytesRead = cryptoStream.Read(output, 0, output.Length); while (bytesRead < output.Length) { int n = cryptoStream.Read(output, bytesRead, output.Length - bytesRead); if (n == 0) throw new InvalidDataException("Cannot decrypt value; stream ended prematurely."); bytesRead += n; } return output; }
-
Do you know where I can find the configuration file @atripp?
You mentioned that code to run too, but where should I put it if I want to run it? Is it like an extension I put in ProGet, or is it something I'd run manually on a command line?
-
@hwittenborn it's generally in
C:\ProgramData\Inedo\SharedConfig\ProGet.config
; here's information about where to find the configuration file:
https://docs.inedo.com/docs/installation-configuration-filesIt's just sample code; you would need to write a C# program (or whatever language you'd like) that follows that same logic to decrypt the content stored in
SecretKeys
using AES128I'm not entirely how
SecretKeys
are persisted, but I think either base64 or hex literals
-
@stevedennis: I'm running ProGet through the Docker images, do you know where I could fine the config in that? I haven't been able to find it anywhere inside of my container.
-
Hi @hwittenborn ,
For Linux/Docker, it's passed-in as an environment variable:
https://docs.inedo.com/docs/installation-linux-supported-environment-variablesIt's possible you don't have one, and in that case, the data won't be encrypted. Note that SecretKeys is just a
byte[]
stored as base64.Alana
-
I confirmed non of the environment variables were set @atripp, but after getting the GPG key running
echo '{gpg_key}' | base64 -d | gpg --show-keys /dev/stdin
is complaining about it being an invalid key.Is the GPG key in that part of the database just in some weird format or something?
-
I'm not sure how much it'll help, but this is the specific error I get from GPG:
gpg: read_block: read error: Invalid packet gpg: import from '/dev/stdin' failed: Invalid keyring
After some further looking it appears that the XML entries in
FeedConfiguration_Xml
just stop without a closing quote/bracket as well, are they supposed to?
-
Unfortunately I have no idea what format gpg is looking for or how these could be used locally by debian. We mostly know the API/repo format, not so much the client tooling.
We use the BouncyCastle encryption library. We simply use that byte array like this:
var keys = new PgpSecretKeyRingBundle(this.Data.SecretKeys); using (var output = new MemoryStream()) { using (var armor = new ArmoredOutputStream(new UndisposableStream(output))) { if (!detached) { armor.BeginClearText(HashAlgorithmTag.Sha512); armor.Write(data, 0, data.Length); armor.EndClearText(); } foreach (PgpSecretKeyRing ring in keys.GetKeyRings()) { var key = ring.GetSecretKey(); var signer = new PgpV3SignatureGenerator(key.PublicKey.Algorithm, HashAlgorithmTag.Sha512); signer.InitSign(PgpSignature.CanonicalTextDocument, key.ExtractPrivateKeyRaw(null)); signer.Update(data, 0, data.Length); signer.Generate().Encode(armor); } } return output.ToArray().Where(b => b != '\r').ToArray(); }
Beyond that no idea how they work. Probably not very helpful, but just FYI
-
I'm pretty sure it's just due to Microsoft SQL's CLI cutting off the XML output @atripp, as the XML object would just be unfinished when viewing in my terminal.
I'm gonna whip together a quick CLI to try to pull it out programmatically later today, and then I can let you know if it all works. I'm quite confident that's what's been causing my issue though.