Skip to content

Commit 3bb4e1f

Browse files
committed
Try to expose RSA keygen
I've tried this out with my own linux server, and the RSA keys generated by this with OpenSSH 9.4 are being accepted.
1 parent 7331977 commit 3bb4e1f

File tree

5 files changed

+158
-0
lines changed

5 files changed

+158
-0
lines changed

bin/git.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ void main(List<String> arguments) {
4949
print("DefaultBranch: $branch");
5050
break;
5151
}
52+
break;
53+
case 'keygen':
54+
var (publicKey, privateKey) = bindings.generateRsaKeys();
55+
print('Public Key: $publicKey');
56+
print('Private Key: $privateKey');
57+
5258
break;
5359
default:
5460
print('Unknown command: $command');

lib/go_git_dart.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,27 @@ class GitBindings {
176176

177177
return branch;
178178
}
179+
180+
(String, String) generateRsaKeys() {
181+
var outputPublicKey = malloc.allocate<Pointer<Char>>(0);
182+
var outputPrivateKey = malloc.allocate<Pointer<Char>>(0);
183+
184+
var retValue = lib.GJGenerateRSAKeys(outputPublicKey, outputPrivateKey);
185+
if (retValue != nullptr) {
186+
var err = retValue.cast<Utf8>().toDartString();
187+
lib.free(retValue.cast());
188+
189+
throw Exception("GenerateRsaKeys failed with error code: $err");
190+
}
191+
192+
var publicKey = outputPublicKey.value.cast<Utf8>().toDartString();
193+
lib.free(outputPublicKey.value.cast());
194+
malloc.free(outputPublicKey);
195+
196+
var privateKey = outputPrivateKey.value.cast<Utf8>().toDartString();
197+
lib.free(outputPrivateKey.value.cast());
198+
malloc.free(outputPrivateKey);
199+
200+
return (publicKey, privateKey);
201+
}
179202
}

lib/go_git_dart_bindings_generated.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,6 +2190,24 @@ class GoGitDartBindings {
21902190
int,
21912191
ffi.Pointer<ffi.Char>,
21922192
ffi.Pointer<ffi.Pointer<ffi.Char>>)>();
2193+
2194+
ffi.Pointer<ffi.Char> GJGenerateRSAKeys(
2195+
ffi.Pointer<ffi.Pointer<ffi.Char>> publicKey,
2196+
ffi.Pointer<ffi.Pointer<ffi.Char>> privateKey,
2197+
) {
2198+
return _GJGenerateRSAKeys(
2199+
publicKey,
2200+
privateKey,
2201+
);
2202+
}
2203+
2204+
late final _GJGenerateRSAKeysPtr = _lookup<
2205+
ffi.NativeFunction<
2206+
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Pointer<ffi.Char>>,
2207+
ffi.Pointer<ffi.Pointer<ffi.Char>>)>>('GJGenerateRSAKeys');
2208+
late final _GJGenerateRSAKeys = _GJGenerateRSAKeysPtr.asFunction<
2209+
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Pointer<ffi.Char>>,
2210+
ffi.Pointer<ffi.Pointer<ffi.Char>>)>();
21932211
}
21942212

21952213
final class __mbstate_t extends ffi.Union {

src/gitjournal.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ func GitDefaultBranch(remoteUrl *C.char, privateKey *C.char, privateKeyLen C.int
5050
return nil
5151
}
5252

53+
//export GJGenerateRSAKeys
54+
func GJGenerateRSAKeys(publicKey **C.char, privateKey **C.char) *C.char {
55+
publicKeyVal, privateKeyVal, err := generateRSAKeys()
56+
if err != nil {
57+
return C.CString(err.Error())
58+
}
59+
60+
*publicKey = C.CString(publicKeyVal)
61+
*privateKey = C.CString(privateKeyVal)
62+
63+
return nil
64+
}
65+
5366
func main() {}
5467

5568
/*

src/keygen.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package main
2+
3+
import (
4+
"crypto/rand"
5+
"crypto/rsa"
6+
"crypto/x509"
7+
"encoding/base64"
8+
"encoding/pem"
9+
"fmt"
10+
11+
"errors"
12+
13+
"golang.org/x/crypto/ssh"
14+
)
15+
16+
// generateRSAKeys generates an RSA public/private key pair
17+
// and returns them as PEM encoded strings.
18+
func generateRSAKeys() (string, string, error) {
19+
// Generate the private key
20+
privateKey, err := rsa.GenerateKey(rand.Reader, 2048*2)
21+
if err != nil {
22+
return "", "", err
23+
}
24+
25+
// Marshal the private key to DER format
26+
privateKeyDER := x509.MarshalPKCS1PrivateKey(privateKey)
27+
28+
// PEM encode the private key
29+
privateKeyBlock := &pem.Block{
30+
Type: "RSA PRIVATE KEY",
31+
Bytes: privateKeyDER,
32+
}
33+
privateKeyPEM := string(pem.EncodeToMemory(privateKeyBlock))
34+
35+
// Generate the public key
36+
publicKey := &privateKey.PublicKey
37+
38+
// Marshal the public key to DER format
39+
publicKeyDER, err := x509.MarshalPKIXPublicKey(publicKey)
40+
if err != nil {
41+
return "", "", err
42+
}
43+
44+
// PEM encode the public key
45+
publicKeyBlock := &pem.Block{
46+
Type: "PUBLIC KEY",
47+
Bytes: publicKeyDER,
48+
}
49+
publicKeyPEM := string(pem.EncodeToMemory(publicKeyBlock))
50+
publicKeyOpenSSH, err := PublicPEMtoOpenSSH([]byte(publicKeyPEM))
51+
if err != nil {
52+
return "", "", err
53+
}
54+
55+
return publicKeyOpenSSH, privateKeyPEM, nil
56+
}
57+
58+
// Converts PEM public key to OpenSSH format to be used in authorized_keys file
59+
// Similar to: "ssh-keygen", "-i", "-m", "pkcs8", "-f", auth_keys_new_path
60+
func PublicPEMtoOpenSSH(pemBytes []byte) (string, error) {
61+
// Decode and get the first block in the PEM file.
62+
// In our case it should be the Public key block.
63+
pemBlock, rest := pem.Decode(pemBytes)
64+
if pemBlock == nil {
65+
return "", errors.New("invalid PEM public key passed, pem.Decode() did not find a public key")
66+
}
67+
if len(rest) > 0 {
68+
return "", errors.New("PEM block contains more than just public key")
69+
}
70+
71+
// Confirm we got the PUBLIC KEY block type
72+
if pemBlock.Type != "PUBLIC KEY" {
73+
return "", fmt.Errorf("ssh: unsupported key type %q", pemBlock.Type)
74+
}
75+
76+
// Convert to rsa
77+
rsaPubKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes)
78+
if err != nil {
79+
return "", fmt.Errorf("x509.parse pki public key: %w", err)
80+
}
81+
82+
// Confirm we got an rsa public key. Returned value is an interface{}
83+
sshKey, ok := rsaPubKey.(*rsa.PublicKey)
84+
if !ok {
85+
return "", fmt.Errorf("invalid PEM passed in from user: %w", err)
86+
}
87+
88+
// Generate the ssh public key
89+
pub, err := ssh.NewPublicKey(sshKey)
90+
if err != nil {
91+
return "", fmt.Errorf("new ssh public key from rsa: %w", err)
92+
}
93+
94+
// Encode to store to file
95+
sshPubKey := base64.StdEncoding.EncodeToString(pub.Marshal())
96+
97+
return "ssh-rsa " + sshPubKey, nil
98+
}

0 commit comments

Comments
 (0)