Closed shueybubbles closed 1 year ago
I think I have adapted the code I found correctly. I will look at what you are building here and see if I can replace any of my own cert native interop calls with use of your module. My draft PR for adding Always Encrypted support to the go-mssqldb driver is at https://github.com/microsoft/go-mssqldb/pull/116
func unmarshalRSA(buf []byte) (*rsa.PrivateKey, error) {
// BCRYPT_RSA_BLOB -- https://learn.microsoft.com/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob
header := struct {
Magic uint32
BitLength uint32
PublicExpSize uint32
ModulusSize uint32
Prime1Size uint32
Prime2Size uint32
}{}
r := bytes.NewReader(buf)
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return nil, err
}
if header.Magic != 0x33415352 { // "RSA3" BCRYPT_RSAFULLPRIVATE_MAGIC
return nil, fmt.Errorf("invalid header magic %x", header.Magic)
}
if header.PublicExpSize > 8 {
return nil, fmt.Errorf("unsupported public exponent size (%d bits)", header.PublicExpSize*8)
}
// the exponent is in BigEndian format, so read the data into the right place in the buffer
exp := make([]byte, 8)
n, err := r.Read(exp[8-header.PublicExpSize:])
if err != nil {
return nil, fmt.Errorf("failed to read public exponent %w", err)
}
if n != int(header.PublicExpSize) {
return nil, fmt.Errorf("failed to read correct public exponent size, read %d expected %d", n, int(header.PublicExpSize))
}
mod := make([]byte, header.ModulusSize)
n, err = r.Read(mod)
if err != nil {
return nil, fmt.Errorf("failed to read modulus %w", err)
}
if n != int(header.ModulusSize) {
return nil, fmt.Errorf("failed to read correct modulus size, read %d expected %d", n, int(header.ModulusSize))
}
pk := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
N: new(big.Int).SetBytes(mod),
E: int(binary.BigEndian.Uint64(exp)),
},
D: new(big.Int),
Primes: make([]*big.Int, 2),
}
prime := make([]byte, header.Prime1Size)
n, err = r.Read(prime)
if err != nil {
return nil, fmt.Errorf("failed to read prime1 %w", err)
}
pk.Primes[0] = new(big.Int).SetBytes(prime)
prime = make([]byte, header.Prime2Size)
n, err = r.Read(prime)
if err != nil {
return nil, fmt.Errorf("failed to read prime2 %w", err)
}
pk.Primes[1] = new(big.Int).SetBytes(prime)
expBytes := make([]byte, 2*header.Prime1Size+header.Prime2Size+header.ModulusSize)
n, err = r.Read(expBytes)
if err != nil {
return nil, fmt.Errorf("Unable to read PrivateExponent %w", err)
}
pk.D = new(big.Int).SetBytes(expBytes[2*header.Prime1Size+header.Prime2Size:])
return pk, nil
}
@qmuntal I've found some Go code to unmarshal an RSA public key, but I need similar code to unmarshal the output of NCryptExportKey to an rsa.PrivateKey instance.
We are doing something similar in GeneratKeyRSA
:
exportRSAKey
returns the rsa blob header as a typed struct and the rest of the payload from where to take the key components. The consumeBigInt
converts each segment of the blob into the corresponding big integer chunk, which can be converted into a big.Int
using new(big.Int).SetBytes(b)
.
I think I have adapted the code I found correctly.
You code looks good, although using bytes.NewReader
forces you to allocate more than necessary, why not directly slice the blob directly, as in consumeBigInt
?.
Also, you are not setting the precomputed values, which are present in the blob between the second prime number and the private exponent (D
).
thx I put a todo in my code to revisit it.
Long term - will there be a centralized push within the Microsoft org to consume a package like yours instead of writing home-grown Windows cert store interop code? The Go windows
standard library stops short of full functionality.
Long term - will there be a centralized push within the Microsoft org to consume a package like yours instead of writing home-grown Windows cert store interop code? The Go windows standard library stops short of full functionality.
I'm not aware of any effort heading on that direction, and I doubt go-crypto-winnative
will ever become a generic cert store interop. Would be nice, anyway.
@qmuntal I've found some Go code to unmarshal an RSA public key, but I need similar code to unmarshal the output of
NCryptExportKey
to anrsa.PrivateKey
instance. I am a novice in this area so some guidance would be appreciated!