Kezdjük a fogadó féllel. A Go nyelv dokumentációját olvasva hamar ráakadhatunk, hogy létezik beépített crypto/rsa csomag. Nem bővelkedik a lehetőségekben, ugyanis csak PKCS#1-et támogat. Remélem nem spoiler, de a Go lesz a szűk keresztmetszet választható sztenderdek közül. Létezik persze külső csomag pl. PKCS#8 támogatással, de mi a biztonsági kockázatát kisebbnek ítéltük a beépített bár gyengébb eljárásnak, mint a külső kevesek által auditált megoldásnak. A crypto/rsa csomagnál maradva az egyetlen lehetőségünk, hogy PSS (Probabilistic signature scheme) aláírásokat hitelesítsünk a VerifyPSS metódussal. Szóval nincs más dolgunk mint az RSA kulcspár publikus részét eljuttatni a virtuális gépre, és már mehet is a hitelesítés.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// rawSign - base64 encoded representation of the signiture | |
// pubPem - public key in PEM format | |
// data - the signed data | |
func CheckSignature(rawSign string, pubPem []byte, data []byte) bool { | |
var err error | |
var sign []byte | |
var pub interface{} | |
sign, err = base64.StdEncoding.DecodeString(rawSign) | |
if err == nil { | |
block, _ := pem.Decode(pubPem) | |
if block != nil { | |
pub, err = x509.ParsePKIXPublicKey(block.Bytes) | |
if err == nil { | |
newHash := crypto.SHA256.New() | |
newHash.Write(data) | |
opts := rsa.PSSOptions{SaltLength: 20} // Java default salt length | |
err = rsa.VerifyPSS(pub.(*rsa.PublicKey), crypto.SHA256, newHash.Sum(nil), sign, &opts) | |
} | |
} | |
} | |
return err == nil | |
} |
Küldés során a kérés teljes törzsét írtuk alá, így nincs más dolgunk mint a kérésből kibányászni a törzset és ellenőrizni a hitelességét.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func Wrap(handler func(w http.ResponseWriter, req *http.Request), signatureKey []byte) http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
body := new(bytes.Buffer) | |
defer r.Body.Close() | |
ioutil.ReadAll(io.TeeReader(r.Body, body)) | |
r.Body = ioutil.NopCloser(body) // We read the body twice, we have to wrap original ReadCloser | |
signature := strings.TrimSpace(r.Header.Get("signature")) | |
if err := CheckSignature(signature, signatureKey, body.Bytes()); err != nil { | |
// Error handling | |
w.WriteHeader(http.StatusNotAcceptable) | |
w.Write([]byte("406 Not Acceptable")) | |
return | |
} | |
http.HandlerFunc(handler).ServeHTTP(w, r) | |
}) | |
} |
Valamint implementálni és regisztrálni a kérés feldolgozót.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func PostItHandler(w http.ResponseWriter, req *http.Request) { | |
w.Write([]byte("ok")) | |
} | |
func RegisterHandler() { | |
signature, _ := ioutil.ReadFile("/path/of/public/key") | |
r := mux.NewRouter() | |
r.Handle("/postit", Wrap(PostItHandler, signature)).Methods("POST") | |
http.Handle("/", r) | |
http.ListenAndServe("8080", nil) | |
} |
Természetesen tesztet is írtam az aláírás ellenőrzésére.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type TestWriter struct { | |
header http.Header | |
status int | |
message string | |
} | |
func (w *TestWriter) Header() http.Header { | |
return w.header | |
} | |
func (w *TestWriter) Write(b []byte) (int, error) { | |
w.message = string(b) | |
return len(b), nil | |
} | |
func (w *TestWriter) WriteHeader(s int) { | |
w.status = s | |
} | |
func TestWrapAllValid(t *testing.T) { | |
pk, _ := rsa.GenerateKey(rand.Reader, 1024) | |
pubDer, _ := x509.MarshalPKIXPublicKey(&pk.PublicKey) | |
pubPem := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Headers: nil, Bytes: pubDer}) | |
content := "body" | |
newHash := crypto.SHA256.New() | |
newHash.Write([]byte(content)) | |
opts := rsa.PSSOptions{SaltLength: 20} | |
sign, _ := rsa.SignPSS(rand.Reader, pk, crypto.SHA256, newHash.Sum(nil), &opts) | |
body := bytes.NewBufferString(content) | |
req, _ := http.NewRequest("GET", "http://valami", body) | |
req.Header.Add("signature", base64.StdEncoding.EncodeToString(sign)) | |
writer := new(TestWriter) | |
writer.header = req.Header | |
handler := Wrap(func(w http.ResponseWriter, req *http.Request) {}, pubPem) | |
handler.ServeHTTP(writer, req) | |
if writer.status != 0 { | |
t.Errorf("writer.status 0 == %d", writer.status) | |
} | |
} |
Miután megvagyunk a hitelesítéssel jöhet az aláírás Java oldalon. Kutattam egy darabig hogyan lehet PSS aláírást Java SE-vel generálni, de mivel a projektünknek már része volt a Bouncy Castle Crypto API, így kézenfekvő volt, hogy azt használjam fel.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// privateKeyPem - private key in PEM format | |
// data - data to signature | |
public static String generateSignature(String privateKeyPem, byte[] data) { | |
try (PEMParser pEMParser = new PEMParser(new StringReader(clarifyPemKey(privateKeyPem)))) { | |
PEMKeyPair pemKeyPair = (PEMKeyPair) pEMParser.readObject(); | |
KeyFactory factory = KeyFactory.getInstance("RSA"); | |
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pemKeyPair.getPublicKeyInfo().getEncoded()); | |
PublicKey publicKey = factory.generatePublic(publicKeySpec); | |
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pemKeyPair.getPrivateKeyInfo().getEncoded()); | |
PrivateKey privateKey = factory.generatePrivate(privateKeySpec); | |
KeyPair kp = new KeyPair(publicKey, privateKey); | |
RSAPrivateKeySpec privKeySpec = factory.getKeySpec(kp.getPrivate(), RSAPrivateKeySpec.class); | |
PSSSigner signer = new PSSSigner(new RSAEngine(), new SHA256Digest(), 20); // be sure we use defautl salt lenght | |
signer.init(true, new RSAKeyParameters(true, privKeySpec.getModulus(), privKeySpec.getPrivateExponent())); | |
signer.update(data, 0, data.length); | |
byte[] signature = signer.generateSignature(); | |
return BaseEncoding.base64().encode(signature); | |
} catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException | CryptoException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private static String clarifyPemKey(String rawPem) { | |
return "-----BEGIN RSA PRIVATE KEY-----\n" + rawPem.replaceAll("-----(.*)-----|\n", "") + "\n-----END RSA PRIVATE KEY-----"; // PEMParser nem kedveli a sortöréseket | |
} |
A Java oldali kulcspár generálással tele van az internet, azzal nem untatnák senkit.