2019年4月6日 星期六

關於 linux mint 的作業系統(軟硬體)升級

最近採購一些電腦零件花了台幣共 6688 元, 硬體升級  CPU 成 AMD R3  2200G(NTD 3068元), 主機板用 MSI B450M PRO-VDH V2(NTD 2370元), 記憶體是 DDR4 8G bytes/2666 Mhz(NTD 1250元), 作業系統用 linux mint 19(linux kernel 是  4.15.0), 開機後, 螢幕呈現兩個畫面, 根本無法操作, 只能關機, 查了網路文章, 說可以試著在 kernel parameters 加上 nomodeset, 真的正常開機了, 聲音, 網路, USB 2.0/3.0 都可以正常運作,  後來又去下載 Ubuntu 18.10 iso(linux kernel 版本是 4.18) 檔, 可能是 grub 在這些硬體下, 無法讀取到 iso 檔的內容, 只好將 linux kernel(vmlinuz) 及 init ramdisk(initrd)  擷取出來放在硬碟的  /boot/kernel418 目錄底下, 稍為更改一些設定, 搜尋 linux mint iso 檔來 boot (啟動) linux 作業系統. 我還是習慣用 linux mint 的介面, 操作比較方便.  我也嘗試將 Ubuntu 18.10 iso 檔內的 kernel 擷取出來, 去啟動 linux mint 19 的 iso 檔, 還真的不需要加入 nomodeset 就可以正常開機, 但聲音出不來, 有可能是 loadable linux kernel module (可載入的動態核心模組)版本不合, 造成無法載入,  只好先放棄, 未來可以嘗試下載 4.19 以上新版本的核心原始碼(kernel source code)自行編譯並更新 linux mint iso 裡面的核心模組, 也許不需加入  nomodeset 核心參數就能啟動. 我的 grub.cfg 設定:

set timeout=3
set default=1
hiddenmenu
menuentry "GPT Linux mint 191 Read Only" {
set root=(hd0,1)
set iso=/boot/linux191.iso
loopback loop $iso
linux   (loop)/casper/vmlinuz    boot=casper   iso-scan/filename=$iso    locale=zh_TW.UTF-8    nomodeset
initrd   (loop)/casper/initrd.lz
}

menuentry "GPT Linux mint 191 iso persistent" {
set root=(hd0,1)
set iso=/boot/linux191.iso
loopback loop $iso
linux   (loop)/casper/vmlinuz    boot=casper   iso-scan/filename=$iso   locale=zh_TW.UTF-8     persistent   nomodeset
initrd   (loop)/casper/initrd.lz
}

menuentry "GPT Ubuntu-18.10 iso" {
set root=(hd0,1)
set iso=/boot/ubuntu-18.10-desktop-amd64.iso
linux    /boot/kernel418/vmlinuz   boot=casper    iso-scan/filename=$iso locale=zh_TW.UTF-8
initrd   /boot/kernel418/initrd
}

menuentry "GPT Test Linux mint 19 iso" {
set root=(hd0,1)
set iso=/boot/linux191.iso
linux    /boot/kernel418/vmlinuz   boot=casper    iso-scan/filename=$iso locale=zh_TW.UTF-8
initrd   /boot/kernel418/initrd
}

Linux Mint 19 開啟終端機執行 inxi  顯示 CPU 資訊:
CPU~Quad core AMD Ryzen 3 2200G with Radeon Vega Graphics (-MCP-) speed/max~1438/3500 MHz Kernel~4.15.0-20-generic x86_64 Up~9 min Mem~1180.1/6980.1MB HDD~1370.3GB(59.9% used) Procs~186 Client~Shell inxi~2.3.56

2019年3月26日 星期二

c++ 善用 smart pointer 在物件建構法中傳回不同類型的物件並統一函式介面的作法

每個物件通常有不同的函式介面, 想要將兩種不同物件的方法統一成相同介面, 可以利用物件建構法將它封裝起來, 用這新類型物件去實例化想要的物件, 達成傳回多型物件(polymorphic class)的目的:
#include  <stdio.h>
struct base    { // 基礎類型

  virtual void work( ){ }//擬統一的函式介面

  virtual  ~base(){ } // 避免繼承者,當在解構式中想刪除指標物件時,編譯器會報出錯務訊息

}; // 先建構基礎物件抽象類型, 統一函式介面


struct A: base { // 繼承 base 物件, 將物件用物件 A 封裝起來
    void work( ) override { printf("A\n");    } // 將方法封裝並實現函式介面
};


struct B: base {// 繼承 base 物件, 將另一物件用物件 B 封裝起來
    void work( ) override { printf("B\n");    } // 同樣將方法封裝並實現函式
};


struct C {     // 一個全新多型類型(polymorphic class)
    base *ptr;// 基礎物件的指標, 可以封裝物件 A 或 B
    ~C( )      { delete ptr; }
    C(int a) {  // 根據輸入值實例化不同物件, 奇數傳回 A, 偶數傳回 B, 等同於傳回不同物件
        if (a%2) ptr = (base *) new A();  // 實例化物件 A 指標
        else       ptr = (base *) new B();  // 實例化物件 B 指標
    }
    void work( ){ ptr->work( ); }// 將多型物件成員用指標連結函式介面,方便使用
};
int main(){
   for(int i=0; i < 10; i++)         C(i).work( );
}
如果害怕指標的用法(像是造成 memory leak or double free), 可以用 smart pointer 將它封裝起來, 讓系統自動處理, 就不用擔心記憶體釋放的問題. c++ 所提供的 std::shared_ptr < > 就是 smart pointer 其中之一用來封裝類型指標的標準樣板函式(STL), 但有一點要注意的是, 用 smart pointer 封裝類型時, 該類型的建構及解構方法必須放在 public: 區, 讓 STL 可以正常呼叫, 否則會出現一堆錯誤訊息. 又或者很不喜歡用指標箭頭的方法, 還可以用左值(&alias) 換化成正常物件, 將方法從指標箭頭轉成正常物件的使用方式.
#include  <stdio.h>
#include  <memory> // smart pointer 要使用 memory 函式庫
typedef std::shared_ptr <base> baseptr; // 封裝成 smart pointer, 讓系統自動管理
struct base    {  // 基礎類型

   virtual void work( ){}
   virtual  ~base(){ } // 避免繼承者若在解構式中想刪除指標物件時會報出錯務訊息

}; // 先建構基礎物件的抽象類型, 統一函式介面


struct A: base { // 繼承 base 類型, 可以將物件用類型 A 封裝起來
    void work( ) override { printf("A\n");  } // 將方法封裝並覆蓋虛擬函式
};


struct B: base {// 繼承 base 類型 , 將另一物件用類型 B 封裝起來
    void work( ) override { printf("B\n");    } // 同樣將方法封裝並覆蓋虛擬函式
};


struct C {  // 一個全新多型類型(polymorphic class)
    baseptr ptr;// 宣告 ptr 成為 smart pointer 可以封裝物件 A 或 B 類型的指標    

    // 無需解構式  ~C( ){ }
    C(int a) {  // 根據輸入值實例化不同物件, 奇數傳回 A, 偶數傳回 B, 等同傳回不同物件
        if (a%2) ptr = baseptr(new A( ));  // 實例化物件 A 指標, 同時轉換成 smart pointer
        else        ptr = baseptr(new B( ));  // 實例化物件 B 指標, 同時轉換成 smart pointer
    }

};


int main(){ // 呼叫時, 先用物件直取多型成員, 接著就能用指標 -> 呼叫函式介面
   for(int i=0; i < 10; i++)  C(i).ptr->work( );
}

2019年3月24日 星期日

linux mint 升級 openssl

Openssl:
1. 至官網下載原始碼 https://github.com/openssl/openssl
2. 解壓縮, 並進入目錄
3. 執行 ./config
4. 執行 make
5. 執行 sudo make install
預設程式庫會被安裝至 /usr/local/lib, 導致 openssl 找不到 libssl.so.3 及 libcrypto.so.3 等動態程式庫, 無法執行, 只要將符號連結過去就可以解決問題:
    sudo ln -sf /usr/local/lib/libcrypto.so.3  /usr/lib
    sudo ln -sf /usr/local/lib/libssl.so.3         /usr/lib

Libressl:
1. 這是另一個源出於 Oenssl 的分支, 程式碼是相容的, 號稱刪除一些不必要的程式碼, 讓它更精減, 目標也要讓程式更安全可靠,  編譯的方式大同小異, 參考文章: http://linuxg.net/how-to-install-libressl-2-1-6-on-linux-systems , 可到官網下載源碼:  https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.9.0.tar.gz
2. 解壓縮, 並進入目錄
3. 執行 ./configure
4. 執行 make
5. 執行 sudo make install
6. 執行 sudo ldconfig
預設程式庫也是安裝至 /usr/local/lib, 如果導致 openssl 找不到 libssl.so.45 及 libcrypto.so.45 等動態程式庫, 只要將符號連結過去就可以解決問題:
    sudo ln -sf /usr/local/lib/libcrypto.so.45.0.1  /usr/lib/libcrypto.so.45
    sudo ln -sf /usr/local/lib/libssl.so.47.0.1         /usr/lib/libssl.so.45

編譯程式時要加上選項參數  -L/usr/local/lib -lssl -lcrypto, 例如:
g++   https.c -L/usr/local/lib -lssl -lcrypto  -pthread -o https
或是編寫一個簡單的 Makefile
#Makefile to link with ssl, crypto, pthead library
https:
    g++  https.c -L/usr/local/lib -lssl -lcrypto -pthread -o$@
clean:
    rm  -f  https
之後只要下一個命令, 不用再打那麼多字
make

Boringssl:
1. 同樣源自於 Openssl 的另一分支 , 由 google 負責維護的 tls/ssl 程式庫, 首先要安裝 cmake 及 golang 兩個必要開發工具:
     sudo apt-get install cmake golang
2. 到官網用 git 下載源碼, 複製整個 boringssl 目錄
     cd $HOME/Downloads
     git clone https://boringssl.googlesource.com/boringssl
3. 進入目錄, 建個子目錄 build, 進入子目錄 build,  cmake .. ,  make
     cd $HOME/Downloads/boringss
     mkdir build
     cd build
     cmake ..
     make
4. 例如要編譯 c 聯結 Boringssl 程式庫, 可以進到工作目錄, 把編譯好的程式庫複製過來作聯結:
     cd $HOME/work/c/https
     cp $HOME/Downloads/boringssl/build/ssl/libssl.a   .
     cp $HOME/Downloads/boringssl/build/crypto/libcrypto.a   .
     g++   https.c    -L.   -lssl   -lcrypto   -pthread
   

c++ 寫一個簡單的 https server

參考文章: https://wiki.openssl.org/index.php/Simple_TLS_Server
先安裝好 openssl 開發檔及準備自我認証的憑證
sudo apt-get install libssl–dev
openssl req -x509 -nodes -days 30 -newkey rsa:2048 -keyout mykey.pem -out mycert.pem
編輯 https  server 主程式:
// server.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
int main()

    SSL_library_init(); 
    SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
    SSL_CTX_set_ecdh_auto(ctx, 1);
    SSL_CTX_use_certificate_file(ctx, "mycert.pem", SSL_FILETYPE_PEM);
    SSL_CTX_use_PrivateKey_file(ctx,  "mykey.pem", SSL_FILETYPE_PEM);
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    int server = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(5000);
    addr.sin_addr.s_addr = INADDR_ANY;
    int  ok = 1;
    setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &ok, sizeof(ok));
    bind(server, (struct sockaddr*)&addr, sizeof(addr));
    listen(server, 10);  
    while(1) {
        int client = accept(server, (struct sockaddr*)&addr, &len);
        printf("Accept client IP= %s : port=%d\n",inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        SSL * ssl = SSL_new(ctx);
        SSL_set_fd(ssl, client);
        SSL_accept(ssl);
        char buf[1024];
        int bytes = SSL_read(ssl, buf, sizeof(buf));
        if (bytes > 0) { buf[bytes]=0; printf("Client: %s", buf); }
        char msg[] = "<html><head><meta charset='UTF-8'></head><Body>歡迎光臨</Body></html >";
        buf[0]=0;
        sprintf(buf            ,"HTTP/1.0 200 OK\n");
        sprintf(buf+strlen(buf),"Server: Welcome\n");
        sprintf(buf+strlen(buf),"Content-Type: text/html\n");
        sprintf(buf+strlen(buf),"Content-Length: %11ld\n",strlen(msg));
        sprintf(buf+strlen(buf),"Connection: close\n\n");  
        sprintf(buf+strlen(buf),"%s",msg);
        SSL_write(ssl, buf, strlen(buf));
        SSL_free(ssl);
        close(client);  
    };
    close(server);
    SSL_CTX_free(ctx);
}
編譯並執行
g++ server.c -lssl -lcrypto  -o server &&  ./server
用瀏覽器瀏覽  https://127.0.0.1:5000 , 如有警告信息, 選取並信任該憑證

c++ 寫一個簡單的 https client

寫 https 的程式基本上就是將 http 的封包透過 TLS(Transport Layer Security:傳輸層安全協議)/SSL 加密後再傳出去, 收到的封包也是先經過 TLS/SSL 解密後還原.參考文章: https://wiki.openssl.org/index.php/SSL/TLS_Client
設定完 https server(以下使用 local host: 127.0.0.1 TCP port: 5000). 安裝 SSL 開發檔:
sudo apt-get install libssl–dev
編輯一個簡單的 https client 文字檔:
// client.c
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <openssl/ssl.h>
struct sessionTLS {
    int sid;
    SSL_CTX *ctx;
    SSL *ssl;
};
void freeTLS(sessionTLS &tls) {
    if (tls.ssl != NULL) SSL_free(tls.ssl);   
    if (tls.sid > 0)     close(tls.sid);
    if (tls.ctx != NULL) SSL_CTX_free(tls.ctx); 

}
void bindSSL(sessionTLS &tls) {
    tls.ssl = SSL_new(tls.ctx);
    if(tls.ssl != NULL ) {
        SSL_set_fd(tls.ssl, tls.sid);
        SSL_connect(tls.ssl);
        printf("\nsession TLS encryption: %s\n", SSL_get_cipher(tls.ssl));
        X509 * CA = SSL_get_peer_certificate(tls.ssl);

        if ( CA != NULL )  {
            char * subject= X509_NAME_oneline(X509_get_subject_name(CA), 0, 0);
            char * issuer = X509_NAME_oneline(X509_get_issuer_name(CA) , 0, 0);
            printf("CA: %s , %s\n", subject, issuer);
            free(subject);
            free(issuer);
            X509_free(CA);
        }
    }
}
sessionTLS connectIP(const char *ipaddress, int tcpPort){
    SSL_library_init();
    sessionTLS tls;// create a new TLS object
    tls.ctx = SSL_CTX_new(SSLv23_method());
    tls.sid = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in host;
    host.sin_family = AF_INET;
    host.sin_port     = htons(tcpPort);
    inet_pton(AF_INET, ipaddress, &host.sin_addr) ;
    connect(tls.sid, (sockaddr*)&host, sizeof(host));
    bindSSL(tls);// bind tls with SSL
    return  tls;
}
int writeTLS(sessionTLS &tls, char *msg, int len) { return SSL_write(tls.ssl, msg, len); }
int  readTLS(sessionTLS &tls, char *buf, int len) { return SSL_read(tls.ssl, buf, len); }
int main() { 
    char msg[]="Hello\n";
    char buf[1024];
    sessionTLS tls = connectIP("127.0.0.1", 5000);
    writeTLS(tls, msg, strlen(msg));
    int bytes = readTLS(tls, buf, sizeof(buf));

    char *ptr = buf;
    while (bytes-- >0) printf("%c", *ptr ++);
    printf("\n");
    freeTLS(tls);
}

用 g++ 編譯並執行:
g++  client.c -lssl -lcrypto && ./a.out

2019年3月23日 星期六

用 Javascript 寫一個任意位元的亂數產生器


function random(maxnum) {
     // 將產生後的亂數用時間函數運算過, 傳回整數並把它限制在 {0, ..., 最大值-1} 之間:
     let srand = Math.floor(Math.random( )*maxnum) ^ new Date().getMilliseconds();
     return  srand % maxnum ;
}
function srandBits(bits) {
       // 產生任意位元數的亂數
        let r = bits % 8; // 以 8 位元為單位(次), 產生一個亂數
        let num = [ ];     // 初始化儲存陣列
        if (r > 0) {
               num.push(random(1 << r)); // 先產生餘數位元的亂數
               bits -= r;
        }
        while (bits > 0) { // 還剩下的位元數, 一次產生一個位元組(8 bits = 1 byte)的亂數
            num.push(random(256)); // 產生 8 位元的亂數, 0 <= 亂數 < 256
            bits -= 8; // 減掉每次產生的 8 位元
        }
        return num; // 傳回陣列
}
console.log("random:" + srandBits(32));

2019年3月19日 星期二

ECDSA 數位簽章

參考文章: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
橢圓曲線數位簽章演算法:
假設  ECC(x,y) 是橢圓曲線方程式, 座標產生器 G 將大整數映設到 ECC 曲線上,  使用質數 p 取餘數(%),   1< n < p-1,  假設 order number = n, nG = O
1.  模除數改用橢圓曲線的 order  number  計算
2. 把私章 d, 1 < d < n -1 映射到橢圓曲線的座標 Q = dG = d*G(1) = G(d) ,  Q 就是簽章者的公鑰
3. 用 sha256 擷取文件特徵值 z, z < p,並選一亂數 k, 映設到橢圓曲線 (x, y) = kG = G(k) , 1 < k < n -1
4. 只取出 G(k)的 x 座標,  r = x, 計算出 k 的模倒數 k-1  = 1/k,   (k * k-1) % n = 1
5. 計算 s = (z + rd) / k  %  n =  (z + rd)*k-1  % n,    1 < s < n - 1
6. (z, r,  s , Q)就是帶數位簽章的文件
驗證方式:
1. 數改用橢圓曲線的 order  number  計算,
2. 計算 s 的模倒數 s-1 = 1/s,  (s * s-1) % n  = 1
3. 計算  u = z*s-1  mod n
4. 計算  v  = r*s-1  mod n
5. 用 ECC 加法運算, 計算出座標 K = uG + vQ =G(z*s-1) + G(rd*s-1) = G( (z+rd)*s-1)=G(k)
6. 取出 K 的 x 座標, 應該要等於 r