웹어플리케이션보안 인증서 활용 2017. 9. 중부대학교 정보보호학과 이병천 교수
차례 1. 웹서버보안(https) 적용 2. 인증서 발급 3. 인증서를 이용한 간편 로그인 4. 서비스 빌드 웹서버 보안을 위한 https 보안프로토콜 적용 2. 인증서 발급 로그인된 사용자에게 서버가 사설인증서 발급 사용자는 로컬스토리지에 인증서 및 개인키 저장하여 활용 3. 인증서를 이용한 간편 로그인 패스워드를 입력하지 않고 개인키로 서명하여 로그인 요청 서버는 사용자의 인증서로 서명 검증 4. 서비스 빌드
1. 웹서버보안(https) 적용 SSL/TLS HTTPS란? 인증서를 활용한 전송계층 보안 프로토콜 HTTP over SSL/TLS
자바스크립트 암호 라이브러리 forge Forge TLS, PKI와 여러 도구들을 포함한 자바스크립트 암호 라이브러리 https://www.npmjs.com/package/node-forge
Forge 설치 서버측 패키지 클라이언트측 패키지 프로젝트 루트폴더로 이동 > npm install node-forge node_modules 폴더에 설치됨 서버측 프로그램에서 다음과 같이 불러서 사용 const forge = require('node-forge'); 클라이언트측 패키지 > cd angular-src Angular4 소스에서 다음과 같이 불러서 사용 import * as forge from 'node-forge';
서버의 자체서명인증서 생성 caCert.js 1. 프로젝트 루트폴더에 ca.Cert.js 생성 2. caCert.js 실행 var forge = require('node-forge'); var fs = require('fs'); var pki = forge.pki; // 1. CA 인증서 생성 // generate a keypair and create an X.509v3 certificate var caKeys = pki.rsa.generateKeyPair(2048); var caCert = pki.createCertificate(); // CA 개인키 파일 저장 console.log(pki.privateKeyToPem(caKeys.privateKey)); fs.writeFileSync("caPrivateKey.pem", pki.privateKeyToPem(caKeys.privateKey)); console.log('CA개인키 저장 - caPrivateKey.pem \n'); caCert.publicKey = caKeys.publicKey; caCert.serialNumber = '01'; caCert.validity.notBefore = new Date(); caCert.validity.notAfter = new Date(); caCert.validity.notAfter.setFullYear(caCert.validity.notBefore.getFullYear() + 1); var caAttrs = [{ //name: 'commonName', // CN shortName: 'CN', value: 'Byoungcheon Lee' }, { //name: 'countryName', // C shortName: 'C', value: 'KR' //name: 'stateOrProvinceName', // ST shortName: 'ST', value: 'Gyeonggi-do' //name: 'localityName', // L shortName: 'L', value: 'Goyang-si' //name: 'organizationName', // O shortName: 'O', value: 'Joongbu Univ.' //name: 'organizationalUnitName', shortName: 'OU', value: 'Dept. of Information Security' }]; caCert.setSubject(caAttrs); caCert.setIssuer(caAttrs); caCert.js caCert.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true name: 'subjectAltName', altNames: [{ type: 6, // URI value: 'http://example.org/' type: 7, // IP ip: '127.0.0.1' }] name: 'subjectKeyIdentifier' }]); // self-sign certificate caCert.sign(caKeys.privateKey); console.log('CA 자체서명인증서 생성'); console.log(pki.certificateToPem(caCert)); var verified = caCert.verify(caCert); console.log('CA인증서 생성 후 검증: '+verified); console.log(); // CA 인증서 저장 fs.writeFileSync("caCert.pem", pki.certificateToPem(caCert)); console.log('CA인증서 저장 - caCert.pem'); 1. 프로젝트 루트폴더에 ca.Cert.js 생성 2. caCert.js 실행 > node caCert.js 3. 개인키와 인증서가 다음 파일로 저장됨 - 개인키: caPrivateKey.pem - 인증서: caCert.pem 위 두개의 파일이 프로젝트 루트폴더에 저장됨을 확인
서버측 App.js 수정 Http 서버 삭제 Https 서버 생성 - 개인키 파일 읽기 - 인증서 파일 읽기 const https = require('https'); const fs = require('fs'); 중략 /* http.createServer(app).listen(port, function(){ console.log('Http server started on port '+port);}); */ https.createServer({ key: fs.readFileSync('caPrivateKey.pem'), cert: fs.readFileSync('caCert.pem') }, app).listen(port, function(){ console.log('Https server started on port '+port); }); Http 서버 삭제 Https 서버 생성 - 개인키 파일 읽기 - 인증서 파일 읽기
클라이언트측 auth.service.ts 수정 보안웹서버 접속을 위해 Http를 https로 수정 서비스 빌드 > ng build prepEndpoint(ep){ //return 'https://isweb.joongbu.ac.kr:3000/'+ep; // Server return 'https://localhost:3000/'+ep; // local }
브라우저로 https 접속
ng serve 상태에서의 접속 http://localhost:4200 으로 접속
2. 인증서 발급 로그인된 사용자가 인증서 발급 요청 서버는 사용자 확인하고 사설인증서 발급 키쌍 생성 개인키는 로컬스토리지에 저장 - privateKey.pem 공개키를 서버로 전송하고 인증서 발급 요청 서버는 사용자 확인하고 사설인증서 발급 발급한 인증서 전송 – cert.pem 서버 인증서도 함께 전송 – caCert.pem 클라이언트는 인증서를 로컬스토리지에 저장
Cert 컴포넌트 생성 인증서 발급 페이지를 위한 cert 컴포넌트 생성 App.module.ts에 등록 확인 Components 폴더로 이동 > cd angular-src/src/app/components > ng g component cert import { CertComponent } from './components/cert/cert.component'; 중략 const appRoutes: Routes = [ {path:'cert', component: CertComponent, canActivate:[AuthGuard]} @NgModule({ declarations: [ CertComponent
메뉴에 등록 Navbar.component.html 수정 인증서발급 인증서삭제
인증서 발급 UI 작성 Cert.component.html 각 필드들에 대한 사용자 입력 ngModel 을 이용한 2-way binding 인증서 발급 요청시 onCertRequest() 함수 실행
인증서 발급 Cert.component.ts 사용자 입력값을 JSON 객체로 변환 forge 객체 생성 certRequest 함수 호출 서버의 응답을 처리 컴포넌트에서 사용되는 사용자 입력 변수 선언 인증서를 저장 사용되는 서비스 선언
인증서 발급 Auth.service.ts 수정 키쌍 생성 Pem 형식으로 변환 로컬스토리지에 저장된 user 객체의 username을 읽어옴 - 인증서 발급 요청의 common값과 같아야만 인증서 발급 신청 진행 로컬스토리지에 개인키를 저장 Auth.service.ts 수정 사용자 입력 정보에 공개키 정보를 추가하여 req 생성 users/cert에 post 형식으로 req를 전송하고 인증서 발급 요청 - 서버의 응답을 받아옴 서버가 보내온 인증서를 로컬스토리지에 저장 - cert: 사용자 인증서 - caCert: 서버 인증서
서버측 routes/users.js 수정 routes/users.js fs 객체 생성: 파일에서 읽어오기 forge 객체 생성: node-forge 이용 서버의 인증서와 개인키를 파일에서 PEM 형식으로 읽어옴 인증서와 개인키 객체 생성
서버측 routes/users.js 수정 인증서 객체 cert 생성 공개키 정보 설정 일련번호, 유효기간 설정 사용자 정보 입력하여 userAttrs 객체 생성 사용자 정보 설정
서버측 routes/users.js 수정 발급자 정보 입력 - 서버인증서 caCert의 정보들을 읽어와서 설정 발급자 정보 설정
서버측 routes/users.js 수정 서버 개인키로 서명하여 인증서 생성 사용자 인증서, 서버인증서를 cert.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true name: 'subjectAltName', altNames: [{ type: 6, // URI value: 'http://example.org/' type: 7, // IP ip: '127.0.0.1' }] name: 'subjectKeyIdentifier' }]); 서버 개인키로 서명하여 인증서 생성 사용자 인증서, 서버인증서를 JSON 형식으로 리턴 확장정보 설정
클라이언트에서 인증서 저장 cert.component.ts routes/users.js 인증서를 저장하고 dashboard로 리다이렉트 auth.service.ts
Dashboard에서 인증서 정보 보여주기 Dashboard.component.html에 인증서 관련 UI 작성 dashboard.component.html 사용자 인증서 서버 인증서 사용자 인증서 유효성 검증
Dashboard에서 인증서 정보 보여주기 forge 객체 생성 인증서 관련 변수 생성 - certPem: 사용자 인증서 - caCertPem: 서버 인증서 - verify: 인증서 유효성 검증 로컬스토리지에서 인증서 정보 읽어오기 인증서 유효성 검증 this.verify = caCert.verify(cert);
Dashboard에서 인증서 정보 보여주기 사용자 인증서 서버 인증서 사용자 인증서 유효성 검증
3. 인증서를 이용한 간편 로그인 패스워드 입력 필요 없음 전자서명 생성하여 서버에 제시
로그인 폼 작성 Login.component.html 왼쪽 ID/Pass 로그인 오른쪽 전자서명 간편 로그인 onLoginSubmit() 함수 실행 username, password 변수 이용 왼쪽 ID/Pass 로그인 오른쪽 전자서명 간편 로그인 onSigLoginSubmit() 함수 실행 username1 변수 이용
로그인 논리 수정 username1 변수 추가 Login.component.ts authenticateSigUser 함수 추가
Auth.service.ts 수정 로컬스토리지에서 개인키, 인증서를 Pem 형식으로 읽어옴 개인키 객체 생성 username, currentTime 정보를 개인키로 서명 서명값을 Hex로 인코딩 (화면표시, 전송) 인증요청을 위한 request 정보 생성 - Username - 현재시간 - 서명값 - 인증서 request를 post로 전송하고 인증 요청 서버측에 users/authenticateSig API 생성 필요
서버측 routes/users.js 수정 DB에서 username 정보 읽어옴 클라이언트의 요청정보를 읽어옴 인증서 객체 생성 전자서명값 생성 인증서에서 공개키 추출 인증서의 subject에서 common 값 추출 시간차이 계산 4가지 검증 - 전자서명이 유효? - 사용자 인증서가 유효? - 시간차이 확인 - 요청자가 인증서 소유자인지?
서버측 routes/users.js 수정 4가지 검증이 모두 유효하면 공개토큰 생성 비밀토큰 생성 success, ptoken, stoken, user 정보를 JSON으로 리턴
로그인 성공하면 Login.component.ts 로그인 성공하면 공개토큰, 비밀토큰, 사용자정보를 저장 dashboard로 리다이렉트
로그인 성공! Username 입력하고 로그인 버튼 클릭 전자서명으로 로그인 성공 공개토큰, 비밀토큰이 생성됨
인증서 삭제 기능 추가 인증서를 삭제할 수 있는 기능이 필요 메뉴에 인증서 삭제 버튼 추가 navbar.component.html <li *ngIf="authService.loggedIn()"><a (click)="onLogoutClick()" href="#">로그아웃</a></li> <li *ngIf="authService.loggedIn()"><a (click)="onDeleteCertClick()" href="#">인증서삭제</a></li> navbar.component.ts auth.service.ts 로컬스토리지에서 다음 정보 삭제 - 사용자 인증서 - 서버 인증서 - 사용자 개인키
4. 서비스 빌드 서비스 빌드 Express 웹서비스로 전체 서비스 접속 가능 공용서버에서 서비스 > cd angular-src > ng build 개발된 angular-src 폴더의 내용이 컴파일되어 public 폴더로 이 전됨 Express 웹서비스로 전체 서비스 접속 가능 공용서버에서 서비스 Auth.service.ts에서 지정된 prepEndpoint(ep)의 리턴주소를 수정 폴더 전체를 서버에 업로드 서버 실행
전체 서비스 완성 Https로 접속 로그인시 토큰 발급 인증서 발급