JavaScriptでECDSA署名してPythonで検証しようとしたら

## JavaScriptでECDSA署名したらPythonで検証できなかった

JavaScriptはWeb Crypto APIで署名

SubtleCrypto: sign()とSubtleCrypto: verify()

https://developer.mozilla.org/ja/docs/Web/API/SubtleCrypto/sign


https://developer.mozilla.org/ja/docs/Web/API/SubtleCrypto/verify


PythonはCryptographyライブラリで検証

cryptography.hazmat.primitives.asymmetric.ec.ECDSA

https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#elliptic-curve-signature-algorithms


上手くいかない。
JavaScript同士、Python同士は問題ないのでお互いのデータのやり取りがおかしい。

SubtleCrypto: sign()の署名値は

> 署名は RFC 6090 で指定されている s1 と s2 の値（RFC 4754 ではそれぞれ r と s として知られている）をビッグエンディアンのバイト配列でエンコードしたものです。
> これらの値はこの順番で連結されます。
> このエンコーダーは IEEE 1363-2000 標準でも 提案されており、 IEEE P1363 形式と呼ばれることもあります。
> これは X.509 の署名構造とは異なり、 OpenSSL などのいくつかのツールやライブラリーでは既定で生成される形式です。

と書いてあります。
出てきた署名値をopensslでチェックしても検証失敗するけど？

> X.509 の署名構造とは異なり
> OpenSSL などのいくつかのツールやライブラリーでは既定で生成される

規定なのに・・・？

## ドキュメントが嘘でした

IEEE P1363はOpenSSLで規定で生成されるどころか対応していません。
翻訳ミス？というか英語でも

> This encoding was also proposed by the IEEE 1363-2000 standard, and is sometimes referred to as the IEEE P1363 format.
> It differs from the X.509 signature structure, which is the default format produced by some tools and libraries such as OpenSSL.

幾つかの機械翻訳にかけみたら

> これはX.509署名構造とは異なり、OpenSSLなどのいくつかのツールやライブラリが生成するデフォルトのフォーマットである。

> これはX.509署名構造とは異なり、これはOpenSSLなどのいくつかのツールやライブラリが生成するデフォルトのフォーマットである。

> これは、OpenSSL などの一部のツールやライブラリによって生成されるデフォルトの形式である X.509 署名構造とは異なります。

> それはX.509署名構造と異なります。X.509はOpenSSLのようなツールやライブラリによって生成されるデフォルトの形式です。

といった感じで、“which is”が指し示すのはX.509かIEEE P1363なのかややこしい？


それはそれとして、 Cryptographyのリファレンスみれば

https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#elliptic-curve-signature-algorithms


ちゃんと
> DER encoded as described in RFC 3279.

って書いてあるので、違う形式だろって気づけるんですけど。


## ちゃんと対応しよう

サポートする形式が合わない以上はどちらかに合わせることとなる。
ここではOpenSSLでもいけるRFC 3279 (X.509)にする。

これは他にも理由があって変換コードを書くのに不慣れなPythonより慣れてるTypeScriptで書きたかった。

簡単に言うとP1363は署名値二つのバイト列を結合しているだけ。
RFC 3279は署名値二つをDERで格納している。

かなり雑に作っているけどコードはこんな感じ

```typescript
const asn1toEcSig = (der:Uint8Array,keySize:number) => {
    if(der[0] !== 0x30){
        return;
    }
    const filedLen = getDerLen(der);
    const seq = der.subarray(2+filedLen[0],2+filedLen[0]+filedLen[1]);
    const rPointLen = getDerLen(seq);
    const rPointDat = paddingInt(seq.subarray(2+rPointLen[0],2+rPointLen[0]+rPointLen[1]),keySize);
    const nextSeq = seq.subarray(2+rPointLen[0]+rPointLen[1]);
    const sPointLen = getDerLen(nextSeq);
    const sPointDat = paddingInt(nextSeq.subarray(2+sPointLen[0],2+sPointLen[0]+sPointLen[1]),keySize);
    return new Uint8Array([...rPointDat,...sPointDat]);
}
const ecSigToASN1Sig = (sig:ArrayBufferLike) => {
    if(sig.byteLength % 2 != 0){
        return;
    }
    const rPointDat = trimArray(new Uint8Array(sig.slice(0,sig.byteLength/2)));
    const rPointLen = getASN1Len(rPointDat.byteLength);
    const rPointBlk = new Uint8Array(1+rPointLen.byteLength+rPointDat.byteLength);
    rPointBlk.set([0x02],0);
    rPointBlk.set(rPointLen,1);
    rPointBlk.set(rPointDat,1+rPointLen.byteLength);
    const sPointDat = trimArray(new Uint8Array(sig.slice(sig.byteLength/2)));
    const sPointLen = getASN1Len(sPointDat.byteLength);
    const sPointBlk = new Uint8Array(1+sPointLen.byteLength+sPointDat.byteLength);
    sPointBlk.set([0x02],0);
    sPointBlk.set(sPointLen,1);
    sPointBlk.set(sPointDat,1+sPointLen.byteLength);
    const seqLen = getASN1Len(rPointBlk.byteLength+sPointBlk.byteLength);
    const ans1Bytes = new Uint8Array(1+seqLen.byteLength+rPointBlk.byteLength+sPointBlk.byteLength);
    let seq = 0;
    ans1Bytes.set([0x30],seq);seq++;
    ans1Bytes.set(seqLen,seq);seq += seqLen.byteLength;
    ans1Bytes.set(rPointBlk,seq);seq += rPointBlk.byteLength;
    ans1Bytes.set(sPointBlk,seq);seq += sPointBlk.byteLength;
    return ans1Bytes;
}
const getASN1Len = (len:number) => {
    if(len<128){
        return new Uint8Array([len]);
    }else{
        const lenlen = Math.floor(len/256)+1;
        const lenBuf = new Uint8Array(1+lenlen);
        lenBuf[0] = 128+lenlen;
        for(let p=1;p<=lenlen;p++){
            lenBuf[p] = len&0xFF
            len = len >> 8;
        }
        return lenBuf;
    }
}
const getDerLen = (der:Uint8Array) => {
    if(der[1]<128){
        return [0,der[1]];
    }else{
        const lenlen = der[1] & 0x7f;
        const size = der.subarray(2,2+lenlen);
        const len = size.reverse().reduce((p,c,i)=>{
            p += c*(256**i);
            return p;
        },0);
        return [lenlen,len]
    }
}
const trimArray = (buf:Uint8Array) => {
    const _temp = [];
    let trim = false;
    for(let i=0;i<buf.byteLength;i++){
        if(buf[i] === 0x00){
            trim = true;
            continue;
        }
        _temp.push(...buf.subarray(i));
        break;
    }
    if(!trim || _temp[0] < 0x80){
        return new Uint8Array(_temp);
    }else{
        return new Uint8Array([0x00,..._temp]);
    }
}
const paddingInt = (uint:Uint8Array,sizeBit:number) => {
    const byteLen = Math.floor((sizeBit+7) / 8);
    const byteDat = new Uint8Array(byteLen);
    byteDat.set(uint,byteLen-uint.byteLength);
    return byteDat;
}
```